12

背景

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

pm install -t -f fullPathToApkFile

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

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)

这似乎也影响了流行的应用程序,例如无法恢复应用程序的“钛备份(专业版)”。

我试过的

查看所写的内容,它似乎没有安装 APK 文件的权限,而/data/local/tmp/.

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

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

     Os.symlink(fullPathToApkFile, symLinkFilePath)
    

    这没有做任何事情。

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

     ln -sf $fullPathToApkFile $symLinkFilePath
    

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

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

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

     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. 为什么操作系统突然需要使用此路径?为什么不使用原始路径,就像安装应用程序的其他方法一样?安装应用程序的其他方法有什么作用,以某种方式避免使用空间路径?

4

2 回答 2

7

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

    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()

它仍然有点危险,但比复制文件更好......


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

我们不支持从设备上的随机目录安装 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. 为什么需要文件大小作为参数?- 不知道,但如果我删除它,它不会工作
于 2018-05-26T14:55:55.457 回答
0

感谢您的回答!我还在其他地方寻找了完整的 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();
于 2021-08-04T20:14:40.937 回答