'Getting file count in a ZIP archive using SAF on Android
I'm currently making a comic viewer app, where user can select a folder that contains zip files using SAF (Intent.ACTION_OPEN_DOCUMENT_TREE). Doing so returns DocumentFile, from which I can use DocumentFile.fromTreeUri() to eventually get List<Uri> of zip files inside that directory. Then, when user selects that file, an Uri is sent to fun unzipFile() inside of my model, that asyncronically:
- Opens input stream to that file
val inputStream = this.contentResolver.openInputStream(zippedFileUri) - Uses
ZipInputStream(either java or zip4j one) to read its contentsZipInputStream(inputStream).use {/*do something with the file*/} - Iterates over all entries, while also checking if it's not a directory using
ZipEntryorLocalFileHeader:
while (zipInputStream.nextEntry.also { localFileHeader = it } != null) {
if (localFileHeader?.isDirectory == true) continue
val bytes = zipInputStream.readBytes()
/* read bitmap from bytes and insert it to a LiveData variable*/
}
This, though not the fastest and prettiest solution, works mostly fine as I then pass that data to a RecyclerView Adapter.
The problem, however, starts when I try getting file count for every zip file in that directory. I don't know any better way to do so than to just reuse my unzipFile() code, but instead of reading bytes - incrementing some variable size+=1. This definitely works, but getting the data from just a few zip files with 70 or so files inside of them takes at best 2 minutes. Even though I insert that data to the database, the problem of reading it the first time still remains.
Below is my code snippet of said function that takes ages. I run it in a model, that gets a list of objects that contain said zippedFileUri (here as it.uri) and tries to get them all at the same time:
viewModelScope.launch {
try {
mangas.map {
async(Dispatchers.IO) {
val pageCount = getZipSize(context.contentResolver, it.uri)
}
}.awaitAll()
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getZipSize(contentResolver: ContentResolver, uri: Uri): Int {
var size: Int = 0
try {
val inputStream = contentResolver.openInputStream(uri)
ZipInputStream(inputStream).use { zipInputStream ->
while (true) {
zipInputStream.nextEntry ?: break
size += 1
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return size
}
Is there any better way to get that information? From looking at Play Store apps I know it can be done, but not sure how. One of them claims it uses minizip-ng for zip files, but I'm clueless when it comes to adding C code to Android.
Using things like ZipFile, be it from zip4j or java won't cut it as the returned Uri is not an actual file, but instead has something to do with ContentResolver, not really sure how that works. While at it, maybe there is also a better way to unzip files in memory?
I tried some better or worse things but to no avail. For example:
val p = FileSystems.getDefault().getPath(zippedFileUri.path)
val f = p.toFile()
val zF = ZipFile(f)
Log.i("V", "${zF.isValidZipFile}") //this returns false
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
