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 (&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 导致的内存泄漏?

你可能感兴趣的:(Android)