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 导致的内存泄漏?
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tingzhushaohua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值