Android中如何通过C++/JNI遍历ZipFile$ZipEntryIterator列表?
问题:JNI遍历ZipFile条目时找不到nextElement方法
背景
我是Android C++/JNI新手,为提升校验安全性,希望将一段Kotlin的CRC校验代码迁移至C++。
Kotlin原代码
val entries = ZipFile(context.packageCodePath).entries().toList() var totalCrc = 0L for (entry in entries) { if (entry.name.startsWith(DEX_FILENAME)) totalCrc += entry.crc }
C++实现代码(存在问题)
jclass zipFileClass = env->FindClass("java/util/zip/ZipFile"); jmethodID zipFileConstructor = env->GetMethodID(zipFileClass, "<init>", "(Ljava/lang/String;)V"); jobject zipFileObject = env->NewObject(zipFileClass, zipFileConstructor, packageCodePath); jmethodID methodId = env->GetMethodID(zipFileClass, "entries", "()Ljava/util/Enumeration;"); jobject zipFileList = env->CallObjectMethod(zipFileObject, methodId); jclass ZipEntryIteratorClass = env->FindClass("java/util/zip/ZipFile$ZipEntryIterator"); // 这行编译无错误 jmethodID mid_Iterator_hasNext = env->GetMethodID(ZipEntryIteratorClass, "hasMoreElements", "()Z"); // 报错:JNI找不到nextElement方法 jmethodID mid_Iterator_next = env->GetMethodID(ZipEntryIteratorClass, "nextElement", "()Ljava/util/zip/ZipEntry;"); // 这行执行无异常 jboolean hasMoreElements = env->CallBooleanMethod(zipFileList, mid_Iterator_hasNext); // 以下代码因异常无法运行 //jobject v = env->CallObjectMethod(zipFileList, mid_Iterator_next); //env->DeleteLocalRef(v); // 最终需要实现的遍历逻辑 //while (hasMoreElements) { //jobject v = env->CallObjectMethod(zipFileList, mid_Iterator_next); // Do something with `v` // 避免本地引用表溢出 //env->DeleteLocalRef(v); //}
运行时异常
runtime.cc:677] JNI DETECTED ERROR IN APPLICATION: JNI CallBooleanMethodV called with pending exception java.lang.NoSuchMethodError: no non-static method "Ljava/util/zip/ZipFile$ZipEntryIterator;.nextElement()Ljava/util/zip/ZipEntry;" runtime.cc:677] at boolean com.something.else.RandomScreenActivity.validateChecksum(java.lang.String) (RandomScreenActivity.kt:-2) runtime.cc:677] at void com.something.else.RandomScreenActivity.onCreate(android.os.Bundle) (RandomScreenActivity.kt:72) runtime.cc:677] at void android.app.Activity.performCreate(android.os.Bundle, android.os.PersistableBundle) (Activity.java:8051) runtime.cc:677] at void android.app.Activity.performCreate(android.os.Bundle) (Activity.java:8031) runtime.cc:677] at void android.app.Instrumentation.callActivityOnCreate(android.app.Activity, android.os.Bundle) (Instrumentation.java:1329) runtime.cc:677] at android.app.Activity android.app.ActivityThread.performLaunchActivity(android.app.ActivityThread$ActivityClientRecord, android.content.Intent) (ActivityThread.java:3608) runtime.cc:677] at android.app.Activity android.app.ActivityThread.handleLaunchActivity(android.app.ActivityThread$ActivityClientRecord, android.app.servertransaction.PendingTransactionActions, android.content.Intent) (ActivityThread.java:3792) runtime.cc:677] at void android.app.servertransaction.LaunchActivityItem.execute(android.app.ClientTransactionHandler, android.os.IBinder, android.app.servertransaction.PendingTransactionActions) (LaunchActivityItem.java:103) runtime.cc:677] at void android.app.servertransaction.TransactionExecutor.executeCallbacks(android.app.servertransaction.ClientTransaction) (TransactionExecutor.java:135) runtime.cc:677] at void android.app.servertransaction.TransactionExecutor.execute(android.app.servertransaction.ClientTransaction) (TransactionExecutor.java:95) runtime.cc:677] at void android.app.ActivityThread$H.handleMessage(android.os.Message) (ActivityThread.java:2210) runtime.cc:677] at void android.os.Handler.dispatchMessage(android.os.Message) (Handler.java:106) runtime.cc:677] at boolean android.os.Looper.loopOnce(android.os.Looper, long, int) (Looper.java:201) runtime.cc:677] at void android.os.Looper.loop() (Looper.java:288) runtime.cc:677] at void android.app.ActivityThread.main(java.lang.String[]) (ActivityThread.java:7839) runtime.cc:677] at java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[]) (Method.java:-2) runtime.cc:677] at void com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run() (RuntimeInit.java:548) runtime.cc:677] at void com.android.internal.os.ZygoteInit.main(java.lang.String[]) (ZygoteInit.java:1003) runtime.cc:677] runtime.cc:677] in call to CallBooleanMethodV runtime.cc:677] from boolean RandomScreenActivity.validateChecksum(java.lang.String)
ZipEntryIterator源码参考
private class ZipEntryIterator implements Enumeration<ZipEntry>, Iterator<ZipEntry> { private int i = 0; public ZipEntryIterator() { ensureOpen(); } public boolean hasMoreElements() { return hasNext(); } public boolean hasNext() { synchronized (ZipFile.this) { ensureOpen(); return i < total; } } public ZipEntry nextElement() { return next(); } public ZipEntry next() { synchronized (ZipFile.this) { ensureOpen(); if (i >= total) { throw new NoSuchElementException(); } long jzentry = getNextEntry(jzfile, i++); if (jzentry == 0) { String message; if (closeRequested) { message = "ZipFile concurrently closed"; } else { message = getZipMessage(ZipFile.this.jzfile); } throw new ZipError("jzentry == 0" + ", jzfile = " + ZipFile.this.jzfile + ", total = " + ZipFile.this.total + ", name = " + ZipFile.this.name + ", i = " + i + ", message = " + message ); } ZipEntry ze = getZipEntry(null, jzentry); freeEntry(jzfile, jzentry); return ze; } } }
解决方案
问题核心是直接访问私有内部类ZipEntryIterator,它属于ZipFile的非公开实现,JNI直接查找会因权限或类路径问题失败。正确的做法是依赖公开的Enumeration接口,修复后的代码如下:
// 获取ZipFile类和构造方法 jclass zipFileClass = env->FindClass("java/util/zip/ZipFile"); jmethodID zipFileConstructor = env->GetMethodID(zipFileClass, "<init>", "(Ljava/lang/String;)V"); jobject zipFileObject = env->NewObject(zipFileClass, zipFileConstructor, packageCodePath); // 获取entries方法,返回公开的Enumeration接口 jmethodID entriesMethod = env->GetMethodID(zipFileClass, "entries", "()Ljava/util/Enumeration;"); jobject enumeration = env->CallObjectMethod(zipFileObject, entriesMethod); // 获取Enumeration接口的公开方法 jclass enumerationClass = env->FindClass("java/util/Enumeration"); jmethodID hasMoreElementsMethod = env->GetMethodID(enumerationClass, "hasMoreElements", "()Z"); jmethodID nextElementMethod = env->GetMethodID(enumerationClass, "nextElement", "()Ljava/lang/Object;"); // 获取ZipEntry类的方法(用于获取name和crc) jclass zipEntryClass = env->FindClass("java/util/zip/ZipEntry"); jmethodID getNameMethod = env->GetMethodID(zipEntryClass, "getName", "()Ljava/lang/String;"); jmethodID getCrcMethod = env->GetMethodID(zipEntryClass, "getCrc", "()J"); // 遍历枚举计算CRC总和 jlong totalCrc = 0L; jstring dexFileName = env->NewStringUTF(DEX_FILENAME); // 假设DEX_FILENAME是预定义常量 while (env->CallBooleanMethod(enumeration, hasMoreElementsMethod)) { jobject zipEntry = env->CallObjectMethod(enumeration, nextElementMethod); // 判断entry名称是否以DEX_FILENAME开头 jstring entryName = (jstring)env->CallObjectMethod(zipEntry, getNameMethod); jclass stringClass = env->FindClass("java/lang/String"); jmethodID startsWithMethod = env->GetMethodID(stringClass, "startsWith", "(Ljava/lang/String;)Z"); jboolean startsWith = env->CallBooleanMethod(entryName, startsWithMethod, dexFileName); if (startsWith) { totalCrc += env->CallLongMethod(zipEntry, getCrcMethod); } // 释放本地引用,避免溢出 env->DeleteLocalRef(zipEntry); env->DeleteLocalRef(entryName); env->DeleteLocalRef(stringClass); } // 清理所有资源 env->DeleteLocalRef(dexFileName); env->DeleteLocalRef(enumeration); env->DeleteLocalRef(zipFileObject); env->DeleteLocalRef(zipFileClass); env->DeleteLocalRef(enumerationClass); env->DeleteLocalRef(zipEntryClass);
关键注意事项
- 始终依赖公开Java接口/类(如
Enumeration、ZipEntry),避免依赖私有内部实现,防止系统版本兼容性问题。 Enumeration的nextElement()返回值是Object,JNI调用后直接当作ZipEntry对象使用即可,无需额外转换。- 必须及时释放本地引用,防止本地引用表溢出导致崩溃。
- 字符串匹配逻辑直接调用Java的
startsWith方法,和原Kotlin代码逻辑完全一致。
内容的提问来源于stack exchange,提问作者04Ant01
相关产品推荐
相关产品推荐

