Open AndroidDeveloperLB opened 5 years ago
Does ByteArrayApkFile meet your need?
You mean this: https://github.com/hsiafan/apk-parser/blob/master/src/main/java/net/dongliu/apk/parser/ByteArrayApkFile.java
This means it has to load the entire APK content in order to parse it. I don't think that's how the Android parser works. Imagine I have to parse multiple files to show them in a list. If I had to load each into memory entirely it would load much slower than loading only what's needed. It will also be a waste for memory, and might cause OOM in case the APK is large. I think it directly goes to the parts that are important: the manifest and later, upon demand, the drawables and the strings (using current configuration, including locale, density, Android version, etc...) .
Via the SAF API , I could give you the InputStream multiple times if needed.
Maybe it's possible to have a ZipInputStream that will take only the manifest file and send it as a byte array to ApkFile? Will the library still be able to parse a byte array that only has the manifest file? The only missing thing in this idea is what to do with app-icons. Maybe will need to parse the manifest file, check which image file is needed, and then use the ZipInputStream again to get it? But what if it's an adaptiveIcon ... So many possible issues...
I tried now to get only the manifest into bytes array. Seems to work, but the parser of this library can't handle it alone. It probably has to have it in an APK file, together with some other stuff.
Here's the code :
AsyncTask.execute {
val packageInfo = packageManager.getPackageInfo(packageName, 0)
val apkFilePath = packageInfo.applicationInfo.publicSourceDir
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 apkFile=ByteArrayApkFile(bytes)
val apkMeta = apkFile.apkMeta
Log.d("AppLog", "apkMeta : $apkMeta ")
}
}
}
Resource table file is a must to get real values of AndroidManifest.xml. Maybe we can provide a minimal api which only read Resource table and AndroidManifest.xml.
I see. Makes sense. But I think it shouldn't read everything that it can. Just what it needs. Does the library first parse the manifest, and then check on the table what is needed, and takes it? Or does it load all possible resources first, and then check what the manifest needs?
OK seems it's almost possible to do it, for manifest and resources files. All I have to do is to load each of those into byte-array and then parse. I wonder though if those files can be too large to handle.
val manifestBytes: ByteArray? = get bytes array from AndroidConstants.MANIFEST_FILE entry
val resourcesBytes: ByteArray? =get bytes array from AndroidConstants.RESOURCE_FILE entry
val xmlTranslator = XmlTranslator()
val resourceTable: ResourceTable =
if (resourcesBytes == null)
ResourceTable()
else {
val resourceTableParser = ResourceTableParser(ByteBuffer.wrap(resourcesBytes))
resourceTableParser.parse()
resourceTableParser.resourceTable
}
val apkMetaTranslator = ApkMetaTranslator(resourceTable, locale)
val binaryXmlParser = BinaryXmlParser(ByteBuffer.wrap(manifestBytes), resourceTable)
binaryXmlParser.locale = locale
binaryXmlParser.xmlStreamer = CompositeXmlStreamer(xmlTranslator, apkMetaTranslator)
binaryXmlParser.parse()
Use InputStream , DocumentFile or Uri instead.
The reason: In the near future, Google will block storage permission, which requires developers to handle files outside of the scope of the current app, to use SAF instead of File and file-path. More information here: https://issuetracker.google.com/issues/128591846
And yes, it's very devastating for many apps and libraries that need to access the files in a normal way. Even the Android framework isn't ready for it, as there are functions such as getPackageArchiveInfo (here) that uses a File or file-path, and have no other alternative.
If you want to test your solution without the storage permission and using just the InputStream, here (the app assumes there is a file named "a.apk" on the root path of the normal storage) : https://issuetracker.google.com/issues/132481545#comment5