Фон
Мое приложение ( здесь ) может выполнять поиск файлов APK во всей файловой системе (не только в установленных приложениях), показывая информацию о каждом из них, позволяя удалять, делиться, устанавливать. ..
Как часть функции хранения с ограниченным объемом в Android Q, Google объявил, что SAF (инфраструктура доступа к хранилищу) заменит обычные разрешения хранения. Это означает, что даже если вы попытаетесь использовать разрешения хранилища, он предоставит доступ только к определенным типам файлов для файла и пути к файлу, которые будут использоваться или полностью помещаться в «песочницу» (записано о здесь ).
Это означает, что многие фреймворки должны будут полагаться на SAF, а не на файл и путь к файлу.
Проблема
Одним из них является packageManager.getPackageArchiveInfo , который с учетом пути к файлу возвращает PackageInfo , который я могу получить различную информацию о:
- имя (в текущей конфигурации), AKA "метка", используя
packageInfo.applicationInfo.loadLabel(packageManager)
. Это основано на текущей конфигурации устройства (локаль и т. Д.)
- имя пакета, используя
packageInfo.packageName
- код версии, используя
packageInfo.versionCode
или packageInfo.longVersionCode
.
- номер версии, используя
packageInfo.versionName
- значок приложения, используя различные способы, в зависимости от текущей конфигурации (плотность и т. Д.):
а. BitmapFactory.decodeResource(packageManager.getResourcesForApplication(applicationInfo),packageInfo.applicationInfo.icon, bitmapOptions)
б. если установлено, AppCompatResources.getDrawable(createPackageContext(packageInfo.packageName, 0), packageInfo.applicationInfo.icon )
с. ResourcesCompat.getDrawable(packageManager.getResourcesForApplication(applicationInfo), packageInfo.applicationInfo.icon, null)
Существует намного больше того, что он возвращает вам, и многое, что не является обязательным, но я думаю, что это основные детали об APK-файлах.
Я надеюсь, что Google предоставит хорошую альтернативу для этого (запрошено здесь и здесь ), потому что в настоящее время я не могу найти никакого хорошего решения для этого .
Что я пробовал
Довольно просто использовать Uri, полученный из SAF, и получить из него InputStream:
@TargetApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
packageInstaller = packageManager.packageInstaller
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "application/vnd.android.package-archive"
startActivityForResult(intent, 1)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && requestCode == 1 && resultCode == Activity.RESULT_OK && resultData != null) {
val uri = resultData.data
val isDocumentUri = DocumentFile.isDocumentUri(this, uri)
if (!isDocumentUri)
return
val documentFile = DocumentFile.fromSingleUri(this, uri)
val inputStream = contentResolver.openInputStream(uri)
//TODO do something with what you got above, to parse it as APK file
Но теперь вы застряли, потому что все, что я видел, требует файла или пути к файлу.
Я пытался найти какую-либо альтернативу, используя платформу Android, но я не смог ее найти. Не только это, но и все библиотеки, которые я нашел, не предлагают ничего подобного.
РЕДАКТИРОВАТЬ: обнаружил, что одна из библиотек, на которые я смотрел ( здесь ) - своего рода возможность анализировать файл APK (включая его ресурсы), используя только поток , но:
В исходном коде используется путь к файлу (класс ApkFile
), и он требует примерно в 10 раз больше, чем обычный синтаксический анализ с использованием платформы Android. Причина, вероятно, в том, что он разбирает все возможное или близко к нему Еще один способ (класс ByteArrayApkFile
) для анализа - использование байтового массива, который включает весь контент APK. Очень расточительно читать весь файл, если вам нужна только небольшая его часть. Кроме того, для этого может потребоваться много памяти, и, как я уже проверял, на самом деле он может достичь OOM, потому что мне нужно поместить весь контент APK в байтовый массив.
Я обнаружил, что иногда не удается проанализировать APK-файлы, которые фреймворк может анализировать нормально ( здесь ). Возможно, это скоро будет исправлено.
Я пытался извлечь только базовый анализ файла APK, и он работал, но он еще хуже с точки зрения скорости ( здесь ). Взял код у одного из классов (под названием AbstractApkFile
). Итак, из файла я получаю только файл манифеста, который не должен занимать много памяти, и анализирую его один, используя библиотеку. Здесь:
AsyncTask.execute {
val packageInfo = packageManager.getPackageInfo(packageName, 0)
val apkFilePath = packageInfo.applicationInfo.publicSourceDir
// I'm using the path only because it's easier this way, but in reality I will have a Uri or inputStream as the input, which is why I use FileInputStream to mimic it.
val zipInputStream = ZipInputStream(FileInputStream(apkFilePath))
while (true) {
val zipEntry = zipInputStream.nextEntry ?: break
if (zipEntry.name.contains("AndroidManifest.xml")) {
Log.d("AppLog", "zipEntry:$zipEntry ${zipEntry.size}")
val bytes = zipInputStream.readBytes()
val xmlTranslator = XmlTranslator()
val resourceTable = ResourceTable()
val locale = Locale.getDefault()
val apkTranslator = ApkMetaTranslator(resourceTable, locale)
val xmlStreamer = CompositeXmlStreamer(xmlTranslator, apkTranslator)
val buffer = ByteBuffer.wrap(bytes)
val binaryXmlParser = BinaryXmlParser(buffer, resourceTable)
binaryXmlParser.locale = locale
binaryXmlParser.xmlStreamer = xmlStreamer
binaryXmlParser.parse()
val apkMeta = apkTranslator.getApkMeta();
Log.d("AppLog", "apkMeta:$apkMeta")
break
}
}
}
Итак, на данный момент это не очень хорошее решение, потому что оно медленное и потому, что получение имени и значка приложения требует от меня предоставления полных данных APK, что может привести к OOM. Если только не найдется способ оптимизировать код библиотеки ...
Вопросы
Как я могу получить информацию об APK (по крайней мере, то, что я упомянул в списке) из InputStream файла APK?
Если на обычной платформе нет альтернативы, где я могу найти такую вещь, которая позволит это?Есть ли какая-нибудь популярная библиотека, которая предлагает его для Android?
Примечание: Конечно, я мог бы скопировать InputStream в файл и затем использовать его, но это очень неэффективно, так как мне придетсяделайте это для каждого файла, который я нахожу, и я трачу на это место и время, потому что файлы уже существуют.