【问题标题】:Install APK using root, handling new limitations of "/data/local/tmp/" folder使用 root 安装 APK,处理“/data/local/tmp/”文件夹的新限制
【发布时间】:2018-11-05 12:15:11
【问题描述】:

背景

到目前为止,我可以通过以下代码使用 root(在应用程序内)安装 APK 文件:

pm install -t -f fullPathToApkFile

如果我想(尝试)安装到 sd 卡:

pm install -t -s fullPathToApkFile

问题

最近,不确定来自哪个 Android 版本(至少在 Android P beta 上存在问题),上述方法失败,向我显示此消息:

avc:  denied  { read } for  scontext=u:r:system_server:s0 tcontext=u:object_r:sdcardfs:s0 tclass=file permissive=0
System server has no access to read file context u:object_r:sdcardfs:s0 (from path /storage/emulated/0/Download/FDroid.apk, context u:r:system_server:s0)
Error: Unable to open file: /storage/emulated/0/Download/FDroid.apk
Consider using a file under /data/local/tmp/
Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
Exception occurred while executing:
java.lang.IllegalArgumentException: Error: Can't open file: /storage/emulated/0/Download/FDroid.apk
    at com.android.server.pm.PackageManagerShellCommand.setParamsSize(PackageManagerShellCommand.java:306)
    at com.android.server.pm.PackageManagerShellCommand.runInstall(PackageManagerShellCommand.java:884)
    at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:138)
    at android.os.ShellCommand.exec(ShellCommand.java:103)
    at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:21125)
    at android.os.Binder.shellCommand(Binder.java:634)
    at android.os.Binder.onTransact(Binder.java:532)
    at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:2806)
    at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:3841)
    at android.os.Binder.execTransact(Binder.java:731)

这似乎也影响了热门应用,例如“Titanium backup (pro)”,无法恢复应用。

我尝试过的

查看所写的内容,它似乎没有安装/data/local/tmp/ 中不存在的APK 文件的权限。

所以我尝试了接下来的事情,看看我是否可以克服它:

  1. 设置对文件的访问权限 (chmod 777) - 没有帮助。
  2. 向我的应用授予存储权限和REQUEST_INSTALL_PACKAGES(使用ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent)权限 - 没有帮助。
  3. 使用官方 API 创建文件的符号链接,使其位于 /data/local/tmp/ 内:

     Os.symlink(fullPathToApkFile, symLinkFilePath)
    

    这没有做任何事情。

  4. 使用此创建符号链接:

     ln -sf $fullPathToApkFile $symLinkFilePath
    

    这部分工作。该文件在那里,我可以在 Total Commander 应用程序中看到它,但是当我尝试检查它是否存在时,并且当我尝试从那里安装 APK 时,它失败了。

  5. 将文件复制/移动(使用cpmv)到/data/local/tmp/ 路径,然后从那里安装。这行得通,但它有缺点:移动是有风险的,因为它会暂时隐藏原始文件,并且会更改原始文件的时间戳。复制很糟糕,因为仅用于安装(即使是临时的)占用了额外的空间,而且这样做会浪费时间。

  6. 复制APK文件,告诉它避免实际复制(意味着硬链接),使用这个命令(取自here):

     cp -p -r -l $fullPathToApkFile $tempFileParentPath"
    

    这不起作用。它给了我这个错误:

     cp: /data/local/tmp/test.apk: Cross-device link
    
  7. 检查在安装应用程序的其他情况下会发生什么。当您通过 IDE 安装时,它实际上会在此特殊路径中创建 APK 文件,但如果您通过 Play Store、简单 APK 安装(通过 Intent)或 adb(通过 PC)安装,则不会。

  8. 在这里也写过这个:https://issuetracker.google.com/issues/80270303

问题

  1. 有没有办法克服在这个特殊路径上使用 root 安装 APK 的缺点?甚至可以完全避免处理这条路径?

  2. 为什么操作系统突然需要使用这个路径?为什么不使用原始路径,就像安装应用程序的其他方法一样?安装应用程序的其他方法有什么作用,以某种方式避免使用空间路径?

【问题讨论】:

    标签: android apk root android-9.0-pie


    【解决方案1】:

    如果您不介意移动过程,一个解决方案是同时保存和恢复原始文件的时间戳,如下所示:

        val tempFileParentPath = "/data/local/tmp/"
        val tempFilePath = tempFileParentPath + File(fullPathToApkFile).name
        val apkTimestampTempFile = File(context.cacheDir, "apkTimestamp")
        apkTimestampTempFile.delete()
        apkTimestampTempFile.mkdirs()
        apkTimestampTempFile.createNewFile()
        root.runCommands("touch -r $fullPathToApkFile ${apkTimestampTempFile.absolutePath}")
        root.runCommands("mv $fullPathToApkFile $tempFileParentPath")
        root.runCommands("pm install -t -f $tempFilePath")
        root.runCommands("mv $tempFilePath $fullPathToApkFile")
        root.runCommands("touch -r ${apkTimestampTempFile.absolutePath} $fullPathToApkFile")
        apkTimestampTempFile.delete()
    

    还是有点危险,不过比复制文件好……


    编辑:谷歌向我展示了一个很好的解决方法(here):

    我们不支持从设备上的随机目录安装 APK。它们要么需要使用“adb install”直接从主机安装,要么您必须流式传输内容才能安装--

    $ cat foo.apk | pm install -S APK_SIZE
    

    虽然我认为他们不支持从随机路径安装 APK 文件(以前一直有效)是不正确的,但解决方法似乎确实有效。我需要在安装 APK 文件的代码中进行更改:

    val length = File(fullPathToApkFile ).length()
    commands.add("cat $fullPathToApkFile | pm install -S $length")
    

    问题是,现在我还有一些其他问题:

    1. 此解决方法是否可以避免将 APK 移动/复制到存储中,并且不会影响原始文件? - 好像是的
    2. 这是否支持任何 APK 文件,甚至是大文件? - 对于占用 433MB 的 APK,它似乎成功了,所以我认为它可以安全地用于所有大小。
    3. 只有 Android P 才需要,对吧? - 到目前为止似乎如此。
    4. 为什么需要文件大小作为参数? - 不知道,但如果我删除它,它将无法工作

    【讨论】:

    • 我知道这是旧的但是你如何执行这个命令。我尝试将其传递为Runtime.getRuntime().exec(commands.toArray())
    • 我的应用是具有系统权限的系统应用,但运行此命令时出现错误提示“无法运行此程序”
    • 如果您可以在这里查看我的问题,那将非常有帮助:- stackoverflow.com/questions/59089567/…
    • @noname 抱歉,我不知道系统应用程序是如何做到的。其实我一直不明白如何在Android Q上转换成系统应用。你是怎么做到的?
    【解决方案2】:

    感谢您的回答!我还在其他地方寻找了完整的 OTA 设置以适用于 Android 10 等等。它 100% 可在运行 Android 10 的三星 Galaxy Tab 10.1 上运行。

    这是一篇带有代码的中型文章: https://medium.com/@jnishu1996/over-the-air-ota-updates-for-android-apps-download-apk-silent-apk-installation-auto-launch-8ee6f342197c

    神奇之处在于以 root 访问权限运行此命令:

                process = Runtime.getRuntime().exec("su");
                out = process.getOutputStream();
                DataOutputStream dataOutputStream = new DataOutputStream(out);
                // Get all file permissions
                dataOutputStream.writeBytes("chmod 777 " + file.getPath() + "\n");
                // Perform silent installation command, all flags are necessary for some reason, only this works reliably post Android 10
                String installCommand = "cat " + file.getAbsolutePath() + "| pm install -d -t -S " + file.length();
                // Data to send to the LaunchActivity to the app knows it got updated and performs necessary functions to notify backend
                // es stands for extraString
                // In LaunchActivity onCreate(), you can get this data by running -> if (getIntent().getStringExtra("OTA").equals("true"))
                String launchCommandIntentArguments = "--es OTA true --es messageId " + MyApplication.mLastSQSMessage.receiptHandle();
                // Start a background thread to wait for 8 seconds before reopening the app's LaunchActivity, and pass necessary arguments
                String launchCommand = "(sleep 8; am start -n co.getpresso.Presso/.activities.LaunchActivity " + launchCommandIntentArguments + ")&";
    
                // The entire command is deployed with a ";" in the middle to launchCommand run after installCommand
                String installAndLaunchCommand = installCommand + "; " + launchCommand;
    
                // begins the installation
                dataOutputStream.writeBytes(installAndLaunchCommand);
                dataOutputStream.flush();
                // Close the stream operation
                dataOutputStream.close();
                out.close();
                int value = process.waitFor();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-29
      • 1970-01-01
      • 2016-11-23
      • 1970-01-01
      相关资源
      最近更新 更多