You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 22:24:56