Search code examples
androidjava-native-interface

Iterate through the ZipFile$ZipEntryIterator list using c++/JNI in Android


I am fairly new with cpp or jni in android. I have few lines of code in kotlin which I want to move it to cpp, reason being want to make the validation in bit more secure way.

Kotlin code:

val entries = ZipFile(context.packageCodePath).entries().toList()
var totalCrc = 0L
for (entry in entries) {
   if (entry.name.startsWith(DEX_FILENAME)) totalCrc += entry.crc
}

What I could achieve in cpp:

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");

**    // This line doesnt have any error from the compiler. **
    jmethodID mid_Iterator_hasNext = env->GetMethodID(ZipEntryIteratorClass, "hasMoreElements", "()Z");
     
**    // I get error at this line, for some reason jni layer is not able to find the nextElement method**
    jmethodID mid_Iterator_next = env->GetMethodID(ZipEntryIteratorClass, "nextElement", "()Ljava/util/zip/ZipEntry;");
**    // This line also is able to run without any exception. **
    jboolean hasMoreElements = env->CallBooleanMethod(zipFileList, mid_Iterator_hasNext);

**// Of course below code doesnt work, due to the exception**

    //jobject v = env->CallObjectMethod(zipFileList, mid_Iterator_next);
    //env->DeleteLocalRef(v);

**// This is the logic which I would eventually have to iterate through the items.**
    //while (hasMoreElements) {
        //jobject v = env->CallObjectMethod(zipFileList, mid_Iterator_next);
        // Do something with `v`

        // Avoid overflowing the local reference table if legList gets large
        //env->DeleteLocalRef(v);
    //}

The exception I get is:

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)

Additionally ZipEntryIterator class from the java.util.ZipFIle class has following methods which can be used to iterate through the items:

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" +
                                       ",\n jzfile = " + ZipFile.this.jzfile +
                                       ",\n total = " + ZipFile.this.total +
                                       ",\n name = " + ZipFile.this.name +
                                       ",\n i = " + i +
                                       ",\n message = " + message
                        );
                }
                ZipEntry ze = getZipEntry(null, jzentry);
                freeEntry(jzfile, jzentry);
                return ze;
            }
        }
    }

Any suggestion how to iterate through the ZipEntryIterator list?


Solution

  • On my virtual Android device, I get the following logcat entry:

    Accessing hidden method Ljava/util/zip/ZipFile$ZipEntryIterator;nextElement()Ljava/util/zip/ZipEntry; (max-target-r, JNI, denied)

    I also found this line in the Android codebase, which confirms the warning message above.

    which makes me think you should stick to the generic Enumeration interface. The ZipFile class returns a generic Enumeration interface, which only returns Objects. So:

    jmethodID mid_Iterator_next = env->GetMethodID(ZipEntryIteratorClass, "nextElement", "()Ljava/lang/Object;");