Skip to content
This repository was archived by the owner on Sep 21, 2020. It is now read-only.

Request: allow to parse APK file without using File or file-path #92

Open
AndroidDeveloperLB opened this issue May 14, 2019 · 8 comments

Comments

@AndroidDeveloperLB
Copy link

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

@hsiafan
Copy link
Owner

hsiafan commented Jun 6, 2019

Does ByteArrayApkFile meet your need?

@AndroidDeveloperLB
Copy link
Author

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...) .

@AndroidDeveloperLB
Copy link
Author

Via the SAF API , I could give you the InputStream multiple times if needed.

@AndroidDeveloperLB
Copy link
Author

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...

@AndroidDeveloperLB
Copy link
Author

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 ")
                }
            }
        }

@hsiafan
Copy link
Owner

hsiafan commented Jun 13, 2019

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.

@AndroidDeveloperLB
Copy link
Author

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?

@AndroidDeveloperLB
Copy link
Author

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()

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants