如何使用“libsu”库(或adb)在Android Q上安装拆分的APK文件?

2024-01-30

背景

使用 root,我知道对于单个 APK 文件,我们可以使用“libsu”库(here https://github.com/topjohnwu/libsu/)这样安装:

val installResult = Shell.su("pm install -t \"$filePath\"").exec()

如果失败了(在新的 Android 版本上失败了,不确定是哪个版本),就这样(写到这个here https://stackoverflow.com/a/50544005/878126):

val installResult = Shell.su("cat \"$filePath\" | pm install -t -S ${apkSource.fileSize}").exec()

我还知道,在安装拆分 APK 文件时,事情会变得非常混乱(如图所示)here https://github.com/Aefyr/SAI/blob/master/app/src/main/java/com/aefyr/sai/installer/rooted/RootedSAIPackageInstaller.java)。首先,您需要使用“pm install-create”命令创建一个会话:

var sessionId: Int? = null
run {
    val sessionIdResult =
            Shell.su("pm install-create -r -t").exec().out
    val sessionIdPattern = Pattern.compile("(\\d+)")
    val sessionIdMatcher = sessionIdPattern.matcher(sessionIdResult[0])
    sessionIdMatcher.find()
    sessionId = Integer.parseInt(sessionIdMatcher.group(1)!!)
    Log.d("AppLog", "sessionId:$sessionId")
}

然后你必须“推送”每个 APK 文件,如下所示:

for (apkSource in fileInfoList) {
    val filePath = File(apkSource.parentFilePath, apkSource.fileName).absolutePath
    Log.d("AppLog", "installing APK : $filePath ${apkSource.fileSize} ")
    val result = Shell.su("pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\" \"$filePath\"").exec()
    Log.d("AppLog", "success pushing apk:${apkSource.fileName} ? ${result.isSuccess}")
}

然后你使用提交更改pm install-commit :

val installResult = Shell.su("pm install-commit $sessionId").exec()

关于这一切的文档:

  install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]
       [-p INHERIT_PACKAGE] [--install-location 0/1/2]
       [--install-reason 0/1/2/3/4] [--originating-uri URI]
       [--referrer URI] [--abi ABI_NAME] [--force-sdk]
       [--preload] [--instantapp] [--full] [--dont-kill]
       [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]
       [--multi-package] [--staged]
    Like "install", but starts an install session.  Use "install-write"
    to push data into the session, and "install-commit" to finish.

  install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]
    Write an apk into the given install session.  If the path is '-', data
    will be read from stdin.  Options are:
      -S: size in bytes of package, required for stdin

  install-commit SESSION_ID
    Commit the given active install session, installing the app.

问题

在 Android P 之前这一切都运行良好,但由于某种原因,它在 Q beta 6 上失败了,向我显示了这个错误:

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/split/base.apk, context u:r:system_server:s0)
Error: Unable to open file: /storage/emulated/0/Download/split/base.apk
Consider using a file under /data/local/tmp/

我尝试过的

这与我发现的单个 APK 的情况类似,here https://stackoverflow.com/a/50544005/878126,所以我想也许这里也可以应用类似的解决方案:

val result = Shell.su("cat $filePath | pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\" \"$filePath\"").exec()

这仍然仅适用于 Android P 及更低版本。

因此,看到我查看的原始代码有效,它使用了 InputStream,正如文档所暗示的那样,这是可能的。这是他们所拥有的:

while (apkSource.nextApk())
     ensureCommandSucceeded(Root.exec(String.format("pm install-write -S %d %d \"%s\"", apkSource.getApkLength(), sessionId, apkSource.getApkName()), apkSource.openApkInputStream()));

所以我尝试的是这样的:

val result = Shell.su("pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\" -")
        .add(SuFileInputStream(filePath)).exec()

遗憾的是这也没有奏效。

问题

我知道我可以复制相同的代码,但是仍然有办法使用该库(因为它会更短、更优雅)?如果是这样,我该怎么办?


虽然很乱,但是试试这个代码。它使用 SuFileInputStream 读取 apk 文件内容,然后将其传输到 install-write 命令中。理论上这应该可以解决问题。

                // getting session id
                val createSessionResult = Shell.su("pm install-create -S $size").
                val sessionIdRegex = "\\[([0-9]+)]".toRegex()
                var sessionId: Int? = null
                for (line in createSessionResult.out) {
                    val result = sessionIdRegex.find(line)?.groupValues?.get(1)?.toInt()
                    if (result != null) {
                        sessionId = result
                        break
                    }
                }

                // writing apks, you might want to extract this to another function
                val writeShellInStream = PipedInputStream()
                PipedOutputStream(writeShellInStream).use { writeShellInOutputStream ->
                    PrintWriter(writeShellInOutputStream).use { writeShellInWriter ->
                        writeShellInWriter.println("pm install-write -S $size $sessionId base") // eventually replace base with split apk name
                        writeShellInWriter.flush()

                        Shell.su(writeShellInStream).submit { writeResult ->
                            if (writeResult.isSuccess) {
                                Shell.su("pm install-commit $sessionId").submit { commitResult ->
                                    // commitResult.isSuccess to check if worked
                                }
                            }
                        }
                        apkInputStream.copyTo(writeShellInOutputStream)
                        writeShellInWriter.println()
                    }
                }

编辑:如果您不需要从流安装,您可能需要首先尝试命令“cat [您的 apk 文件] | pm install-write -S [大小] [sessionId] [基本/拆分 apk 名称]”。如果 cat 不起作用,请尝试“dd if=[apk file]”。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用“libsu”库(或adb)在Android Q上安装拆分的APK文件? 的相关文章

随机推荐