背景
我的应用程序(这里)可以在整个文件系统(不仅仅是已安装的应用程序)中搜索 APK 文件,显示每个应用程序的信息,允许删除、共享、安装...
作为 Android Q 范围存储功能的一部分,Google 宣布 SAF(存储访问框架)将取代正常的存储权限。这意味着即使您尝试使用存储权限,它也只会授予对特定类型文件的访问权限,以便使用或完全沙盒化文件和文件路径(写在这里)。
这意味着很多框架将需要依赖 SAF 而不是 File 和 file-path。
问题
其中之一是packageManager.getPackageArchiveInfo,它给定一个文件路径,返回 PackageInfo,我可以得到各种信息:
- 名称(在当前配置上),又名“标签”,使用
packageInfo.applicationInfo.loadLabel(packageManager)
. 这基于设备的当前配置(区域设置等...) - 包名,使用
packageInfo.packageName
- 版本代码,使用
packageInfo.versionCode
orpackageInfo.longVersionCode
。 - 版本号,使用
packageInfo.versionName
- 应用程序图标,使用各种方式,基于当前配置(密度等...):
一个。BitmapFactory.decodeResource(packageManager.getResourcesForApplication(applicationInfo),packageInfo.applicationInfo.icon, bitmapOptions)
湾。如果安装,AppCompatResources.getDrawable(createPackageContext(packageInfo.packageName, 0), packageInfo.applicationInfo.icon )
C。ResourcesCompat.getDrawable(packageManager.getResourcesForApplication(applicationInfo), packageInfo.applicationInfo.icon, null)
它返回给你的还有很多,还有很多是可选的,但我认为这些是关于 APK 文件的基本细节。
我希望谷歌会为此提供一个很好的替代方案(在此处和此处请求),因为目前我找不到任何好的解决方案。
我试过的
使用从 SAF 获得的 Uri 并从中获得 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
),比使用 Android 框架正常解析要多出约 10 倍。原因可能是它解析了所有可能的或接近它的东西。另一种ByteArrayApkFile
解析方式(class is )是使用包含整个 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 复制到一个文件中然后使用它,但这非常低效,因为我必须为找到的每个文件都这样做,而且这样做会浪费空间和时间,因为文件已经存在.
编辑:在找到解决方法(heregetPackageArchiveInfo
)以通过(on )获取有关 APK 的非常基本信息后"/proc/self/fd/" + fileDescriptor.fd
,我仍然找不到任何方法来获取app-label和app-icon。请,如果有人知道如何单独使用 SAW(没有存储权限),请告诉我。
我已经为此设置了新的赏金,希望有人也能找到一些解决方法。
由于我发现了一个新发现,我提出了新的赏金:一个名为“ Solid Explorer ”的应用程序以 API 29 为目标,但使用 SAF 它仍然可以显示 APK 信息,包括应用程序名称和图标。
即使在最初针对 API 29 时,它也没有显示任何有关 APK 文件的信息,包括图标和应用程序名称。
尝试了一个名为“插件检测器”的应用程序,我找不到该应用程序用于此目的的任何特殊库,这意味着可以使用普通框架来完成它,而无需非常特殊的技巧。
编辑:关于“Solid Explorer”,似乎他们只使用“requestLegacyExternalStorage”的特殊标志,所以他们不单独使用 SAF,而是使用普通框架。
所以,如果有人知道如何单独使用 SAF 获取应用程序名称和应用程序图标(并且可以在工作示例中显示它),请告诉我。
编辑:似乎APK-parser 库可以很好地获取应用程序名称和图标,但对于图标它有一些问题:
- 限定词有点错误,你需要找到最适合你的情况。
- 对于自适应图标,它可以获得 PNG而不是 VectorDrawable。
- 对于 VectorDrawable,它只获取byte-array。不知道如何将其转换为真正的 VectorDrawable。