Фон
Мне нужно проанализировать некоторые zip-файлы различных типов (получение содержимого некоторых внутренних файлов для той или иной цели, включая получение их имен).
Некоторые файлы недоступны по пути к файлу, поскольку Android имеет Uri для доступа к ним, а иногда zip-файл находится внутри другого zip-файла. С pu sh для использования SAF в некоторых случаях еще менее возможно использовать путь к файлу.
Для этого у нас есть 2 основных способа обработки: ZipFile class и ZipInputStream class.
Проблема
Когда у нас есть путь к файлу, ZipFile - идеальное решение. Он также очень эффективен с точки зрения скорости.
Однако в остальных случаях ZipInputStream может вызвать проблемы, такие как этот , у которого есть проблемы с c zip-файлом, и вызвать это исключение:
java.util.zip.ZipException: only DEFLATED entries can have EXT descriptor
at java.util.zip.ZipInputStream.readLOC(ZipInputStream.java:321)
at java.util.zip.ZipInputStream.getNextEntry(ZipInputStream.java:124)
То, что я пробовал
Единственным всегда работающим решением было бы скопировать файл в другое место, где вы могли бы проанализировать его с помощью ZipFile, но это неэффективно и требует, чтобы у вас было свободное хранилище, а также удалите файл, когда вы закончите с ним.
Итак, я обнаружил, что Apache имеет красивый, чистый Java библиотека ( здесь ) для анализа Zip-файлов, и по какой-то причине ее решение InputStream (называемое «ZipArchiveInputStream») кажется даже более эффективным, чем собственный класс ZipInputStream.
В отличие от того, что есть в собственном фреймворке, библиотека предлагает немного больше гибкости. Я мог бы, например, загрузить весь zip-файл в массив байтов и позволить библиотеке обрабатывать его как обычно, и это работает даже для проблемных c Zip-файлов, о которых я упоминал:
org.apache.commons.compress.archivers.zip.ZipFile(SeekableInMemoryByteChannel(byteArray)).use { zipFile ->
for (entry in zipFile.entries) {
val name = entry.name
... // use the zipFile like you do with native framework
gradle dependency:
// http://commons.apache.org/proper/commons-compress/ https://mvnrepository.com/artifact/org.apache.commons/commons-compress
implementation 'org.apache.commons:commons-compress:1.20'
К сожалению, это не всегда возможно, потому что это зависит от того, что кучная память хранит весь zip-файл, а на Android он становится еще более ограниченным, потому что размер кучи может быть относительно небольшим (размер кучи может составлять 100 МБ, а размер файла - 200 МБ). В отличие от P C, в котором может быть установлена огромная кучная память, для Android он совсем не гибкий.
Итак, я искал решение с JNI вместо этого, чтобы иметь все ZIP файл загружается туда в байтовый массив, не идя в кучу (по крайней мере, не полностью). Это может быть более приятным обходным путем, потому что, если бы ZIP-файл мог поместиться в ОЗУ устройства, а не в куче, это могло бы помешать мне добраться до OOM, а также не нужно было бы иметь дополнительный файл.
Я нашел эта библиотека называется larray , что кажется многообещающим, но, к сожалению, когда я попытался использовать ее, она потерпела крах, потому что ее требования включают наличие полной JVM, что означает непригодность для Android.
РЕДАКТИРОВАТЬ: видя, что я не могу найти ни одной библиотеки и какого-либо встроенного класса, я сам попытался использовать JNI. К сожалению, мне это очень надоело, и я посмотрел на старый репозиторий, который давно сделал go для выполнения некоторых операций с растровыми изображениями ( здесь ). Вот что я придумал:
native-lib. cpp
#include <jni.h>
#include <android/log.h>
#include <cstdio>
#include <android/bitmap.h>
#include <cstring>
#include <unistd.h>
class JniBytesArray {
public:
uint32_t *_storedData;
JniBytesArray() {
_storedData = NULL;
}
};
extern "C" {
JNIEXPORT jobject JNICALL Java_com_lb_myapplication_JniByteArrayHolder_allocate(
JNIEnv *env, jobject obj, jlong size) {
auto *jniBytesArray = new JniBytesArray();
auto *array = new uint32_t[size];
for (int i = 0; i < size; ++i)
array[i] = 0;
jniBytesArray->_storedData = array;
return env->NewDirectByteBuffer(jniBytesArray, 0);
}
}
JniByteArrayHolder.kt
class JniByteArrayHolder {
external fun allocate(size: Long): ByteBuffer
companion object {
init {
System.loadLibrary("native-lib")
}
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
thread {
printMemStats()
val jniByteArrayHolder = JniByteArrayHolder()
val byteBuffer = jniByteArrayHolder.allocate(1L * 1024L)
printMemStats()
}
}
fun printMemStats() {
val memoryInfo = ActivityManager.MemoryInfo()
(getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo(memoryInfo)
val nativeHeapSize = memoryInfo.totalMem
val nativeHeapFreeSize = memoryInfo.availMem
val usedMemInBytes = nativeHeapSize - nativeHeapFreeSize
val usedMemInPercentage = usedMemInBytes * 100 / nativeHeapSize
Log.d("AppLog", "total:${Formatter.formatFileSize(this, nativeHeapSize)} " +
"free:${Formatter.formatFileSize(this, nativeHeapFreeSize)} " +
"used:${Formatter.formatFileSize(this, usedMemInBytes)} ($usedMemInPercentage%)")
}
Это кажется неправильным, потому что если я попытаюсь создать массив байтов размером 1 ГБ, используя jniByteArrayHolder.allocate(1L * 1024L * 1024L * 1024L)
, он выйдет из строя без каких-либо исключений или журналов ошибок.
Вопросы
Можно ли использовать JNI для библиотеки Apache, чтобы он обрабатывал содержимое ZIP-файла, которое содержится в «мире» JNI?
Если да, то как это сделать? Есть ли пример того, как это сделать? Есть ли для этого класс? Или надо самому реализовать? Если да, то не могли бы вы показать, как это делается в JNI?
Если это невозможно, как еще можно это сделать? Может быть альтернатива тому, что есть у Apache?
Почему решение JNI не работает? Как я могу эффективно скопировать байты из потока в байтовый массив JNI (я предполагаю, что это будет через буфер)?