-
项目背景
在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 导致的内存泄漏?