使用root安装APK,处理“/ data / local / tmp /”文件夹

时间:2018-05-26 07:18:15

标签: android apk root android-9.0-pie

背景

到目前为止,我可以使用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(专业版)”,无法恢复应用。

我尝试了什么

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

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

  1. 设置对文件的访问权限(chmod 777) - 没有帮助。
  2. 向我的应用授予对存储和REQUEST_INSTALL_PACKAGES(使用ACTION_MANAGE_UNKNOWN_APP_SOURCES意图)的权限 - 没有帮助。
  3. 为该文件创建一个符号链接,以便它使用官方API在/data/local/tmp/内:

     Os.symlink(fullPathToApkFile, symLinkFilePath)
    

    这没有做任何事情。

  4. 使用以下方法创建符号链接:

     ln -sf $fullPathToApkFile $symLinkFilePath
    

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

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

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

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

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

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

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

  9. 问题

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

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

2 个答案:

答案 0 :(得分:0)

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

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

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

编辑:Google已向我展示了一个很好的解决方法(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. 为什么需要文件大小作为参数? - 不知道,但如果我删除它,它将无法正常工作

答案 1 :(得分: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();