JNI開發:JNI層新起的函數中(C回調函數中)調用JAVA層的接口

  • 項目背景
    在JNI層的實現中,需要將C回調函數的數據返回給Java層,爲此嘗試在C的回調函數中直接調用Java層接口,沒有成功,似乎是線程問題;然後在C的回調函數中通過AttachCurrentThread開啓線程調用,在完成調用以後再DetachCurrentThread釋放運行環境;也沒有成功,似乎是釋放的問題;

  • 解決方法
    JNI在C的回調函數 中 調用Java的函數,總結一般過程如下:

1.先獲取到JNIEnv*,需要通過AttachCurrentThread獲取到Java函數的運行環境;

 static JNIEnv* getJNIEnv(JavaVM* pJavaVM)
    {
    	JavaVMAttachArgs lJavaVMAttachArgs;
    	lJavaVMAttachArgs.version = JNI_VERSION_1_6;
    	lJavaVMAttachArgs.name = "NativeCallBack";
    	lJavaVMAttachArgs.group = NULL;
    	JNIEnv* lEnv;
    	if((pJavaVM)->AttachCurrentThread(&lEnv, &lJavaVMAttachArgs) != JNI_OK){
    		lEnv = NULL;
    	}
    	return lEnv;
    }

2.通過JNI函數調用Java的函數
3.最後調用DetachCurrentThread來釋放getJNIEnv中獲取的運行運行環境

正如背景介紹,這裏邊存在不同的線程調用問題;以及開啓的線程env環境釋放問題。

參照以上步驟,最終在C回調函數中調用Java接口,並總結原因在代碼註釋中:

JavaVM    *mVm;  //將JNI_OnLoad中的JavaVM *jvm定義一個全局變量,便於重新獲取當前線程下的env;
static   jobject   mNativeInterface; //將JNI_OnLoad中的找到的jclass定義一個全局變量,便於在該線程中獲取類對象

class DataCallback : public IDataCallback {
public:
    void receivedData(char *data, int length) {
        __android_log_print(ANDROID_LOG_INFO, "jni", "receivedData0");

        JNIEnv *jniEnv;
        // 1.獲取當前Java線程下的env;若成功則說明當前執行的線程是Java線程,否則就是C++線程,需要調用getJNIEnv來獲取Java環境。
        // 2.此處需要重新通過全局的mVm獲取env,開始沒有獲取導致失敗,因爲這是不同的線程,對應不同的env。
        if (mVm->GetEnv(reinterpret_cast<void **> (&jniEnv), JNI_VERSION_1_6) != JNI_OK) {
            //c++ thread; need acquire java environment by the function of getJNIEnv
            __android_log_print(ANDROID_LOG_INFO, "jni", "c++ thread");
            jniEnv = getJNIEnv(mVm);  //參照上一段代碼
            needDetach = true;
        }

	// 3.獲取Java環境失敗,退出調用
        if (jniEnv == NULL) {
            __android_log_print(ANDROID_LOG_INFO, "jni", "getJNIEnv failed");
            return;
        }

	// 4.通過全局引用獲取類對象
        jclass clazz = jniEnv->GetObjectClass(mNativeInterface);
        if (clazz == NULL) {
            __android_log_print(ANDROID_LOG_INFO, "GetObjectClass", "find class error");
            if(needDetach){
                mVm->DetachCurrentThread();
            }
            return;
        }

	// 5.拷貝數據,調用Java層的靜態方法,此處setReceiveData爲java層的靜態方法,在該線程中調用容易被優化,以至於找不到方法,因此需要在調用之前,在Java層調用下該函數,並拿到該方法引用。
        jbyteArray jbData = jniEnv->NewByteArray(length);
        jniEnv->SetByteArrayRegion(jbData, 0, length, (jbyte *)data);
        jniEnv->CallVoidMethod(clazz, setReceiveData, jbData);

        jniEnv->DeleteLocalRef(jbData);
        jniEnv->DeleteLocalRef(clazz);
        // 6.如果是C++運行線程,才需要DetachCurrentThread,否則會引起detaching thread with interp frames
        if(needDetach){
            mVm->DetachCurrentThread();
        }
    }
};
  • 重要結論(關於Jvm和JNIEnv、內存釋放問題的疑問)
    1. Android環境中,每個進程只能誕生一個JavaVM對象,被所有線程共享。在VM加載*.so程序庫時,會先調用JNI_OnLoad()函數,在JNI_OnLoad()函數中會將JavaVM指針對象保存到c層JNI的全局變量中。
    2. JNIEnv對象和線程是一一對應的關係;
    3. Jvm和JNIEnv釋放問題?JVM 中 Java Heap 的內存泄漏?JVM 內存中 native memory 的內存泄漏?
    4. 從操作系統角度看,JVM 在運行時和其它進程沒有本質區別。在系統級別上,它們具有同樣的調度機制,同樣的內存分配方式,同樣的內存格局。JVM 進程空間中,Java Heap 以外的內存空間稱爲 JVM 的 native memory。進程的很多資源都是存儲在 JVM 的 native memory 中,例如載入的代碼映像,線程的堆棧,線程的管理控制塊,JVM 的靜態數據、全局數據等等。也包括 JNI 程序中 native code 分配到的資源。
    5. Local Reference 導致的內存泄漏?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章