JNI FindClass Error in Native Thread

| 分类 Android  | 标签 android  jni  findclass 

Table of Contents

1 Q:

If we try to find java class in native thread, it probably results with an error: Can't find class.

2 A:

Here is the explanation:

FAQ: Why didn't FindClass find my class?

(Most of this advice applies equally well to failures to find methods with GetMethodID or GetStaticMethodID, or fields with GetFieldID or GetStaticFieldID.)

Make sure that the class name string has the correct format. JNI class names start with the package name and are separated with slashes, such as java/lang/String. If you're looking up an array class, you need to start with the appropriate number of square brackets and must also wrap the class with 'L' and ';', so a one-dimensional array of String would be [Ljava/lang/String;. If you're looking up an inner class, use '$' rather than '.'. In general, using javap on the .class file is a good way to find out the internal name of your class.

If you're using ProGuard, make sure that ProGuard didn't strip out your class. This can happen if your class/method/field is only used from JNI.

If the class name looks right, you could be running into a class loader issue. FindClass wants to start the class search in the class loader associated with your code. It examines the call stack, which will look something like:

    Foo.myfunc(Native Method)
    Foo.main(Foo.java:10)

The topmost method is Foo.myfunc. FindClass finds the ClassLoader object associated with the Foo class and uses that.

This usually does what you want. You can get into trouble if you create a thread yourself (perhaps by calling pthread_create and then attaching it with AttachCurrentThread). Now there are no stack frames from your application. If you call FindClass from this thread, the JavaVM will start in the "system" class loader instead of the one associated with your application, so attempts to find app-specific classes will fail.

There are a few ways to work around this:

  • Do your FindClass lookups once, in JNI_OnLoad, and cache the class references for later use. Any FindClass calls made as part of executing JNI_OnLoad will use the class loader associated with the function that called System.loadLibrary (this is a special rule, provided to make library initialization more convenient). If your app code is loading the library, FindClass will use the correct class loader.
  • Pass an instance of the class into the functions that need it, by declaring your native method to take a Class argument and then passing Foo.class in.
  • Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly. This requires some effort.

2.1 Solution 1: cache jclass:

Here is what I did in JNI_OnLoad() to cache jclass:

 1: #include "android/log.h"
 2: 
 3: #undef PDEBUG
 4: #define PDEBUG(fmt, ...)                                                \
 5:     do {                                                                \
 6:         __android_log_print(ANDROID_LOG_ERROR,                          \
 7:                             "TDEBUG", "%s(%d)-%s: " fmt,                \
 8:                             __FILE__, __LINE__,__FUNCTION__, ##  __VA_ARGS__); \
 9:     } while(0)
10: 
11: typedef struct _nativeClientEnv {
12:     JavaVM* vm;
13:     JNIEnv* env;
14:     jobject obj;
15:     jclass cls;
16:     jmethodID cbID;
17: } nativeClientEnv;
18: 
19: static nativeClientEnv* cEnv = NULL;
20: 
21: JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
22: {
23:     cEnv     = ZALLOC1(nativeClientEnv);
24:     cEnv->vm = vm;
25: 
26:     JNIEnv* env = NULL;
27:     if ((*vm)->GetEnv(vm, &env, JNI_VERSION_1_6) != JNI_OK) {
28:         return -1;
29:     }
30: 
31:     jclass cls = (*env)->FindClass(env,  "com/remotedx/jni/NativeClientWrapper");
32:     if (!cls) {
33:         PDEBUG ("Can't find java class...\n");
34:         exit(1);
35:     }
36: 
37:     cEnv->cls = (*env)->NewGlobalRef(env, cls);
38:     return JNI_VERSION_1_6;
39: }

Then in another thread, I used the jclass find in JNI_OnLoad() to find proper jmethodID:

 1: static void callback(client_status status, rxp_header* rxp, void* priv)
 2: {
 3:     if (status == THREAD_CREATED) {
 4:         if((*(cEnv->vm))->AttachCurrentThread(cEnv->vm, &cEnv->env, NULL) != JNI_OK) {
 5:             PDEBUG ("Failed to attach current thread..\n");
 6:             return;
 7:         }
 8:         if (!cEnv->cls)  {
 9:             PDEBUG ("Failed to find proper class(%lX) or method(%lX)\n",
10:                     cEnv->cls, cEnv->cbID);
11:             return;
12:         }
13: 
14:         cEnv->cbID = (*(cEnv->env))->GetStaticMethodID(cEnv->env, cEnv->cls,
15:                                                        "statusCallback", "(IJ)V");
16:         if (!cEnv->cbID)  {
17:             PDEBUG ("Failed to find method...\n");
18:             exit(1);
19:         }
20:     }
21:     else if (status == THREAD_END) {
22:         PDEBUG ("here\n");
23:         if((*(cEnv->vm))->DetachCurrentThread(cEnv->vm) != JNI_OK) {
24:             PDEBUG("DetachCurrentThread() failed");
25:         }
26:     }
27:     else
28:     {
29:         switch (status)
30:         {
31:             default:
32:             {
33:                 PDEBUG ("here, status: %d\n", (int)status);
34:                 PDEBUG ("cEnv: %p -- %p -- %lX --> %lX\n",
35:                         cEnv, cEnv->env, cEnv->cls, cEnv->cbID);
36:                 (*cEnv->env)->CallStaticVoidMethod(cEnv->env, cEnv->cls, cEnv->cbID, (int)status, NULL);
37:                 PDEBUG ("finish...\n");
38:                 break;
39:             }
40:         }
41:     }
42: }

You can get source code at github .

2.2 Solution 2: cache class loader :

There is another solution in StackOverflow, which caches class loader :

 1: static jmethodID gFindClassMethod;
 2: 
 3: JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
 4:     gJvm = pjvm;  // cache the JavaVM pointer
 5:     auto env = getEnv();
 6:     //replace with one of your classes in the line below
 7:     auto randomClass = env->FindClass("com/example/RandomClass");
 8:     jclass classClass = env->GetObjectClass(randomClass);
 9:     auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
10:     auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
11:                                              "()Ljava/lang/ClassLoader;");
12:     gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
13:     gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
14:                                     "(Ljava/lang/String;)Ljava/lang/Class;");
15: 
16:     return JNI_VERSION_1_6;
17: }
18: 
19: jclass findClass(const char* name) {
20:     return static_cast<jclass>(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
21: }
22: 
23: JNIEnv* getEnv() {
24:     JNIEnv *env;
25:     int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
26:     if(status < 0) {
27:         status = gJvm->AttachCurrentThread(&env, NULL);
28:         if(status < 0) {
29:             return nullptr;
30:         }
31:     }
32:     return env;
33: }

上一篇     下一篇