问题
Background
Using root, I know that for a single APK file, we can use the "libsu" library (here) to install as such:
val installResult = Shell.su("pm install -t \"$filePath\"").exec()
And if that failed (fails on new Android versions, not sure from which), as such (written about this here):
val installResult = Shell.su("cat \"$filePath\" | pm install -t -S ${apkSource.fileSize}").exec()
I also know that things got quite messy when it comes to installing split APK files (as shown here). First you need to create a session, using the "pm install-create" command:
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")
}
Then you have to "push" each of the APK files, as such:
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}")
}
And then you commit the changes using pm install-commit
:
val installResult = Shell.su("pm install-commit $sessionId").exec()
Docs about it all:
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.
The problem
This all worked fine till Android P, but for some reason, it failed on Q beta 6, showing me this error:
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/
What I've tried
This is similar to the case I've found for the single APK , here, so I thought that maybe a similar solution can be applied here too:
val result = Shell.su("cat $filePath | pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\" \"$filePath\"").exec()
This still worked only on Android P and below.
So seeing that the original code I looked at worked, it uses an InputStream, which as the docs imply, is possible. Here's what they had:
while (apkSource.nextApk())
ensureCommandSucceeded(Root.exec(String.format("pm install-write -S %d %d \"%s\"", apkSource.getApkLength(), sessionId, apkSource.getApkName()), apkSource.openApkInputStream()));
So what I tried is as such:
val result = Shell.su("pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\" -")
.add(SuFileInputStream(filePath)).exec()
Sadly this also didn't work.
The question
I know I could just copy the same code, but is there still a way to use the library instead (because it will be shorter and more elegant)? If so, how could I do it?
回答1:
It's messy, but try this code. It is using SuFileInputStream to read the apk file contents, which it then pipes into the install-write command. This should in theory fix the problem.
// 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()
}
}
Edit: You might want to try the command "cat [your apk file] | pm install-write -S [size] [sessionId] [base / split apk name]" first if you don't need to install from stream. If cat doesn't work, try "dd if=[apk file]" instead.
来源:https://stackoverflow.com/questions/57468292/how-to-use-libsu-library-or-adb-to-install-split-apk-files-on-android-q