Reputation: 354
This is for an experimental Android project done in Java. What I need is to read a ZIP file from an external card (as fast as possible, if possible) and return the InputStream
corresponding to an entry each time it is needed by an OSMDroid custom tiles provider.
I can do this pretty easily when serving a ZIP from the primary (or whatever it's called within their mangled filesystem) disk but when trying to read from an external card, the only option in newer versions of Android is to use their shiny SAF.
When the path is not a relative one but a URI
to an external card, I use a DocumentFile
to retrieve the ZIP's InputStream
(apparently, the only way to do it).
DocumentFile file = DocumentFile.fromSingleUri(this, uri);
ContentResolver contentResolver = getContentResolver();
InputStream inStream = contentResolver.openInputStream(file.getUri());
Question 1:
What would be amazing at this point, where using DocumentFile
already took its toll on performance due to how slower it is than the good ol File
, is to be able to have that inStream
converted into a ZipFile
. Or somehow directly get a ZipFile
from DocumentFile
. Is that possible? Maybe get a File
from DocumentFile
then a ZipFile
from that?
I didn't find any solution so I went on with the InputStream
, which I converted to a ZipInputStream
. The problem in my code is that I can only run through it once, when what I need is to do it every time a file's stream from the Zip is needed.
public class MyZipFileArchive implements IArchiveFile {
protected static ZipInputStream mZipInputStream;
}
public void init(InputStream inStream) {
mZipInputStream = new ZipInputStream(inStream);
}
public InputStream getInputStream(ITileSource pTileSource, long pMapTileIndex) {
final String path = pTileSource.getTileRelativeFilenameString(pMapTileIndex);
ZipEntry entry = null;
while ((entry = mZipInputStream.getNextEntry()) != null) {
if (path.equals(entry.getName())) {
return mZipInputStream;
}
}
return null;
}
Question 2:
The first time the getInputStream
is called it returns what it finds but now the stream is at its end and the second time it does it, null
is returned.
So, I need a way to either reset the stream, if possible, each time the function is called, or use another approach. I tried storing each ZipEntry
into a List
at the beginning, then loop through it. It doesn't work. Also, I did not find a way to convert ZipEntry
into the InputStream
required to be returned.
How would you tackle this? Basically, I need to use DocumentFile
and I need to get a ZipFile
from it or some fast way to find its entries.
Upvotes: 1
Views: 486
Reputation: 652
You need to read the stream from the DocumentFile in one pass:
// the uri to your zip document in SAF
context.contentResolver.openInputStream(uri).use { inputStream ->
ZipInputStream(inputStream).use { zipInputStream ->
generateSequence { zipInputStream.nextEntry }.forEach { entry ->
if (entry.name.startsWith("/")) {
throw SecurityException("Dangerous Zip entry: $entry")
}
if (entry.isDirectory) {
// create the directory for entry.name
} else {
// get outputStream for dest file for entry.name
outputStream.use { zipOutputStream ->
zipInputStream.copyTo(zipOutputStream)
}
}
}
}
}
Upvotes: 0