1. 程式人生 > >JNI子執行緒FindClass失敗

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 <jni.h>
#include <pthread.h>
#include <android/log.h>
#include <stdio.h>
#include <unistd.h>

#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();
 }
	
}

參考
JNI開發注意事項集錦