Как получить информацию об APK-файле в файловой системе (а не только в установленных) без использования File или file-path? - PullRequest
40 голосов
/ 26 мая 2019

Фон

Мое приложение ( здесь ) может выполнять поиск файлов APK во всей файловой системе (не только в установленных приложениях), показывая информацию о каждом из них, позволяя удалять, делиться, устанавливать. ..

Как часть функции хранения с ограниченным объемом в Android Q, Google объявил, что SAF (инфраструктура доступа к хранилищу) заменит обычные разрешения хранения. Это означает, что даже если вы попытаетесь использовать разрешения хранилища, он предоставит доступ только к определенным типам файлов для файла и пути к файлу, которые будут использоваться или полностью помещаться в «песочницу» (записано о здесь ).

Это означает, что многие фреймворки должны будут полагаться на SAF, а не на файл и путь к файлу.

Проблема

Одним из них является packageManager.getPackageArchiveInfo , который с учетом пути к файлу возвращает PackageInfo , который я могу получить различную информацию о:

  1. имя (в текущей конфигурации), AKA "метка", используя packageInfo.applicationInfo.loadLabel(packageManager). Это основано на текущей конфигурации устройства (локаль и т. Д.)
  2. имя пакета, используя packageInfo.packageName
  3. код версии, используя packageInfo.versionCode или packageInfo.longVersionCode.
  4. номер версии, используя packageInfo.versionName
  5. значок приложения, используя различные способы, в зависимости от текущей конфигурации (плотность и т. Д.):

а. 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 (включая его ресурсы), используя только поток , но:

  1. В исходном коде используется путь к файлу (класс ApkFile), и он требует примерно в 10 раз больше, чем обычный синтаксический анализ с использованием платформы Android. Причина, вероятно, в том, что он разбирает все возможное или близко к нему Еще один способ (класс ByteArrayApkFile) для анализа - использование байтового массива, который включает весь контент APK. Очень расточительно читать весь файл, если вам нужна только небольшая его часть. Кроме того, для этого может потребоваться много памяти, и, как я уже проверял, на самом деле он может достичь OOM, потому что мне нужно поместить весь контент APK в байтовый массив.

  2. Я обнаружил, что иногда не удается проанализировать APK-файлы, которые фреймворк может анализировать нормально ( здесь ). Возможно, это скоро будет исправлено.

  3. Я пытался извлечь только базовый анализ файла 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. Если только не найдется способ оптимизировать код библиотеки ...

Вопросы

  1. Как я могу получить информацию об APK (по крайней мере, то, что я упомянул в списке) из InputStream файла APK?

  2. Если на обычной платформе нет альтернативы, где я могу найти такую ​​вещь, которая позволит это?Есть ли какая-нибудь популярная библиотека, которая предлагает его для Android?

Примечание: Конечно, я мог бы скопировать InputStream в файл и затем использовать его, но это очень неэффективно, так как мне придетсяделайте это для каждого файла, который я нахожу, и я трачу на это место и время, потому что файлы уже существуют.

1 Ответ

0 голосов
/ 21 июня 2019

Используйте ниже код

/**
* Get the apk path of this application.
*
* @param context any context (e.g. an Activity or a Service)
* @return full apk file path, or null if an exception happened (it should not happen)
*/
public static String getApkName(Context context) {
    String packageName = context.getPackageName();
    PackageManager pm = context.getPackageManager();
    try {
        ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
        String apk = ai.publicSourceDir;
        return apk;
    } catch (Throwable x) {
        return null;
    }
}
...