Фон
Используя root, я знаю, что для одного файла APK мы можем использовать библиотеку «libsu» (здесь ) для установки как таковой:
val installResult = Shell.su("pm install -t \"$filePath\"").exec()
И если это не удалось (сбой в новых версиях Android, не знаю, от какой), как таковой (об этом написано здесьа>):
val installResult = Shell.su("cat \"$filePath\" | pm install -t -S ${apkSource.fileSize}").exec()
Я также знаю, что все становится довольно грязно, когда дело доходит до установки разделенных файлов APK (как показано здесь). Сначала вам нужно создать сессию, используя команду «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, здесь, поэтому я подумал, что, возможно, подобное решение можно применить и здесь:
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()
К сожалению, это тоже не сработало.
Вопрос
Я знаю, что могу просто скопировать тот же код, но есть ли способ использовать вместо него библиотеку (потому что она будет короче и элегантнее)? Если да, то как я мог это сделать?