Android|JNI子线程FindClass失败

1、在C语言里创建子线程 ?在进行jni开发时,Java调用C语言一般都处于主线程中的,但是使用JNI开发,很多情况都是需要开启子线程的(毕竟不能阻塞主线程),那么如何开启子线程尼?很简单,代码如下:

void void *th_fun(void *arg) {}//是子线程的回调函数,我认为就相当于Java里的`Runnable`任务,但是在C语言里是可以传递参数的。 pthread_create(&tid, NULL/*很少用到*/, th_fun/*子线程回调*/, (void *) "no1"/*传递给子线程的参数*/);

2、在子线程使用env ?有时候在子线程会去调用Java方法,那么如何调用尼?一般我们都会通过env->FIndClass来调用,但是如何在子线程回调函数里拿到env尼?将env设为全局引用,这是一个解决方案,但是env本就是与线程相关的,如果设为全局引用给其他线程调用,这样就搞混乱了,所以不好。那么如何解决尼?其实我们可以通过JavaVM来解决,JavaVM代表的是Java虚拟机,所有工作都是从JavaVM开始的,每个Java程序代表一个JavaVM,Android里每个Android程序都的JavaVM都是一样的。解决方案如下:
static JavaVM *javaVM; //动态库加载时会执行 //兼容Android SDK 2.2之后,2.2没有这个函数 JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { LOGI("%s", "JNI_OnLoad"); javaVM = vm; return JNI_VERSION_1_4; } void *th_fun(void *arg) { JNIEnv *env = NULL; int isAttacked = 0; int status = (*javaVM)->GetEnv(javaVM, (void **) &env, JNI_VERSION_1_4); if (status < 0) { isAttacked = 1; (*javaVM)->AttachCurrentThread(javaVM, &env, NULL); } }

3、在子线程FindClass失败 ?有时候会在子线程去调用Java类,但是在我们创建的子线程(通过pthread_create创建)中调用FindClass查找非系统类时会失败(查找系统类不会失败),返回值为NULL,为什么尼?这是因为通过AttachCurrentThread附加到虚拟机的线程在查找类时只会通过系统类加载器进行查找,不会通过应用类加载器进行查找,因此可以加载系统类,但是不能加载非系统类,如自己在java层定义的类会返回NULL。
?那么如何解决尼?主要有以下两个方案
  • 获取classLoader,通过调用classLoader的loadClass来加载自定义类。适合自定义类比较多的情况
  • 在主线程创建一个全局的自定义类引用。适合自定义类比较少的情况
#include #include #include #include #include #define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"dadou",FORMAT,##__VA_ARGS__); #define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"dadou",FORMAT,##__VA_ARGS__); static JavaVM *javaVM; static jobject class_loader_obj_ = NULL; static jmethodID find_class_mid_ = NULL; static jclass global_ref = NULL; //动态库加载时会执行 //兼容Android SDK 2.2之后,2.2没有这个函数 JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { LOGI("%s", "JNI_OnLoad"); javaVM = vm; LOGI("a=%d,b=%d", vm == NULL, javaVM == NULL); //--------------------------------------------方案一-------------------------------------------- // JNIEnv *env = NULL; // int status = (*javaVM)->GetEnv(javaVM, (void **) &env, JNI_VERSION_1_4); // if (status == JNI_OK) {//我认为最好在JNI_OnLoad里拿到classLoader的全局引用 // jclass classLoaderClass = (*env)->FindClass(env, "java/lang/ClassLoader"); // jclass adapterClass = (*env)->FindClass(env, "com/example/thread/UUIDUtils"); // if (adapterClass) { // jmethodID getClassLoader = (*env)->GetStaticMethodID(env, adapterClass, // "getClassLoader", // "()Ljava/lang/ClassLoader; "); // jobject obj = (*env)->CallStaticObjectMethod(env, adapterClass, getClassLoader); // class_loader_obj_ = (*env)->NewGlobalRef(env, obj); // find_class_mid_ = (*env)->GetMethodID(env, classLoaderClass, "loadClass", // "(Ljava/lang/String; )Ljava/lang/Class; "); // (*env)->DeleteLocalRef(env, classLoaderClass); // (*env)->DeleteLocalRef(env, adapterClass); // (*env)->DeleteLocalRef(env, obj); // } // } //---------------------------------------------------------------------------------------------- return JNI_VERSION_1_4; }//子线程的回调 /** * 在子线程中,不能通过env->FindClass来获取自定义类,(*env)->FindClass(env, "com/example/thread/UUIDUtils"); 返回NULL, * (*env)->FindClass(env,"java/lang/String"); 能够正确的返回 * 解决方案一:获取classLoader,通过调用classLoader的loadClass来加载自定义类。适合自定义类比较多的情况 * 解决方案二:在主线程创建一个全局的自定义类引用。适合自定义类比较少的情况 * @param arg * @return */ void *th_fun(void *arg) { JNIEnv *env = NULL; int isAttacked = 0; int status = (*javaVM)->GetEnv(javaVM, (void **) &env, JNI_VERSION_1_4); if (status < 0) { isAttacked = 1; (*javaVM)->AttachCurrentThread(javaVM, &env, NULL); } jclass clazz = NULL; //--------------------------------------------方案一-------------------------------------------- // jstring class_name = (*env)->NewStringUTF(env, "com/example/thread/UUIDUtils"); // clazz = (*env)->CallObjectMethod(env, class_loader_obj_, find_class_mid_, // class_name); // (*env)->DeleteLocalRef(env, class_name); // if (clazz != NULL) { // jmethodID get_mid = (*env)->GetStaticMethodID(env, clazz, "get", // "()Ljava/lang/String; "); // jobject uuid = (*env)->CallStaticObjectMethod(env, clazz, get_mid); // char *uuid_cstr = (char *) (*env)->GetStringUTFChars(env, uuid, NULL); // LOGI("uuid : %s", uuid_cstr); // (*env)->ReleaseStringUTFChars(env, uuid, uuid_cstr); // } //---------------------------------------------------------------------------------------------- //--------------------------------------------方案二-------------------------------------------- if (global_ref != NULL) { jmethodID get_mid = (*env)->GetStaticMethodID(env, global_ref, "get", "()Ljava/lang/String; "); jobject uuid = (*env)->CallStaticObjectMethod(env, global_ref, get_mid); char *uuid_cstr = (char *) (*env)->GetStringUTFChars(env, uuid, NULL); LOGI("uuid : %s", uuid_cstr); (*env)->ReleaseStringUTFChars(env, uuid, uuid_cstr); } //----------------------------------------------------------------------------------------------char *no = (char *) arg; int i = 0; for (i = 0; i < 5; i++) { LOGI("thread %s, i:%d", no, i); if (i == 4) { if (class_loader_obj_ != NULL) { (*env)->DeleteGlobalRef(env, class_loader_obj_); } //采用方案二时需要释放全局引用 //---------------释放--------- if (global_ref != NULL) { LOGI("%s", "开始释放全局引用") (*env)->DeleteGlobalRef(env, global_ref); } //----------------------------//下面的函数必须最后执行,在后面再使用env会报错 if (isAttacked == 1) { //解除关联 (*javaVM)->DetachCurrentThread(javaVM); //必须在离开当前线程之前执行 } pthread_exit((void *) 0); } sleep(1); }}//JavaVM 代表的是Java虚拟机,所有工作都是从JavaVM开始的,每个Java程序代表一个JavaVM,Android里每个Android程序都的JavaVM都是一样的 //可以通过JavaVM获取到每个线程关联的JNIEnv//如何获取JavaVM? //1.在JNI_OnLoad函数中获取 //2.(*env)->GetJavaVM(env,&javaVM); //每个线程都有独立的JNIEnv JNIEXPORT jstring JNICALL Java_com_example_thread_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */ object) { char str[] = "Hello from C"; jclass clazz = (*env)->FindClass(env, "com/example/thread/UUIDUtils"); global_ref = (*env)->NewGlobalRef(env, clazz); pthread_t tid; //子线程id //创建一个子线程 pthread_create(&tid, NULL/*很少用到*/, th_fun/*子线程回调*/, (void *) "no1"/*传递给子线程的参数*/); return (*env)->NewStringUTF(env, str); }

?下面就是Java方法
public class UUIDUtils { public static ClassLoader getClassLoader() { return UUIDUtils.class.getClassLoader(); } public static String get(){ return UUID.randomUUID().toString(); } }

【Android|JNI子线程FindClass失败】参考
JNI开发注意事项集锦

    推荐阅读