-
項目背景
在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、內存釋放問題的疑問)
- Android環境中,每個進程只能誕生一個JavaVM對象,被所有線程共享。在VM加載*.so程序庫時,會先調用JNI_OnLoad()函數,在JNI_OnLoad()函數中會將JavaVM指針對象保存到c層JNI的全局變量中。
- JNIEnv對象和線程是一一對應的關係;
- Jvm和JNIEnv釋放問題?JVM 中 Java Heap 的內存泄漏?JVM 內存中 native memory 的內存泄漏?
- 從操作系統角度看,JVM 在運行時和其它進程沒有本質區別。在系統級別上,它們具有同樣的調度機制,同樣的內存分配方式,同樣的內存格局。JVM 進程空間中,Java Heap 以外的內存空間稱爲 JVM 的 native memory。進程的很多資源都是存儲在 JVM 的 native memory 中,例如載入的代碼映像,線程的堆棧,線程的管理控制塊,JVM 的靜態數據、全局數據等等。也包括 JNI 程序中 native code 分配到的資源。
- Local Reference 導致的內存泄漏?