Android Native层回调Java方法的线程问题

最近项目有native层的开发需求。以前没有接触过,正好练手。

最先遇到的是JNI层的线程问题。

有这么一个需求: 从网络上获取视频流的帧数据,每一帧都要回调到java层去处理h264的解码,以及播放。所以调用非常频繁,10ms到300ms调用一次。

刚开始是下面这样的代码:

// 负责回调java方法把帧数据传上去。

void OnRecvVideoData(void *observer, h264_decode_struct *data) {
    jobject obj = (jobject) observer;
    JNIEnv *jniEnv = NULL;
    g_JavaVM->AttachCurrentThread(&jniEnv, NULL);
    jclass cls = jniEnv->GetObjectClass(obj);
    jmethodID onRecvVideoDataM = jniEnv->GetMethodID(cls, "onRecvVideoData", "([BIJJ)V");
    jniEnv->DeleteLocalRef(cls);
    jbyteArray barr = jniEnv->NewByteArray(data->data_len);
    if (barr == NULL) return;
    jniEnv->SetByteArrayRegion(barr, (jint) 0, data->data_len, (jbyte *) data->data);
    jniEnv->CallVoidMethod(obj, onRecvVideoDataM, barr, data->frame_type,
                           data->pts, data->pts);
    jniEnv->DeleteLocalRef(barr);
    g_JavaVM->DetachCurrentThread();
}

导致的问题是:视频不流畅,而且log里显示GC在疯狂的工作 ! 

很好奇到底是怎么回事。然后猜测是不是每一次回调,在java层都会产生新的线程?!

    @NativeCallback
    public void onRecvVideoData(byte[] nal, int nalType, long dts, long pts) {
        if (mProvider == null)
            return;

        Log.d(TAG, "name:" + Thread.currentThread().getName() + " id:" + Thread.currentThread().getId());

        mProvider.onVideoData(nalPacket);
    }

经过验证后,真的发现每一次回调线程id都不一样! 怪不得gc log在疯狂的输出。

接下来就想办法解决这个问题。

调试发现:

    JNIEnv *jniEnv = NULL;
    g_JavaVM->AttachCurrentThread(&jniEnv, NULL);

         //在这中间多次调用java层方法,线程id都是一样的。


    g_JavaVM->DetachCurrentThread();

也就是说和JNIEnv有关。查了JNI的官方文档,说是每一个想回调java层的c/c++线程, 都需要JVM给生成一个JNIEnv与c/c++线程关联,这样才能在c/c++线程的方法里用JNIEnv调用方法。而新的JNIEnv,JVM都会为java层生成一个新的线程。attach就是c/c++线程关联新生成的java线程。

好像有点绕,简单说就是这样的对应关系: 新c/c++线程 -----》新JNIEnv-----》新java线程。

知道了原因后,把代码改成下面就好了:

//生成JNIEnv, 和java方法句柄onRecvVideoDataM。在线程开始的时候调用。
extern "C" void
jvm_attach_process(void *observer, void **jni_env, void **data_callback) {
    LOGD("video_attach_process start");

    jobject obj = (jobject) observer;
    JNIEnv *jniEnv = NULL;
    int state = g_JavaVM->GetEnv(jni_env, JNI_VERSION_1_6);
    if (state == JNI_EDETACHED) {
        g_JavaVM->AttachCurrentThread(&jniEnv, NULL);
    }

    jclass cls = jniEnv->GetObjectClass(obj);
    jmethodID onRecvVideoDataM = jniEnv->GetMethodID(cls, "onRecvVideoData", "([BIJJ)V");
    *data_callback = onRecvVideoDataM;
    jniEnv->DeleteLocalRef(cls);
    *jni_env = jniEnv;
    LOGD("video_attach_process end");
}


//删除jniEnv

extern "C" void
jvm_detach_process() {
    g_JavaVM->DetachCurrentThread();
}
//线程方法
static THREAD_RETVAL rtp_video_thread(void *arg) {

    Callback callbacks = *((Callback*)arg);
    void *jni_env = NULL;
    void *data_callback = NULL;


    callbacks.jvm_attach_process(callbacks.cls, &jni_env, &data_callback);


     //接收每一帧视频数据h264_data


     //最终回调到OnRecVideoData()。 回调到java层的时候需要jni_env。
    callbacks.video_process(callbacks.cls, data_callback, jni_env, &h264_data);



    callbacks.jvm_detach_process();
    return 0;
}

 

  • JVM的GetEnv(jni_env, JNI_VERSION_1_6)方法是判断jni_env有没有被attach上,如果没有被attach的话jni_env会被赋值为NULL
  • JVM的AttachCurrentThread(&jniEnv, NULL)方法是生成一个java层的线程,和与之相对应的jniEnv。
  • JVM的DetachCurrentThread(&jniEnv, NULL)方法就是关闭之前AttachCurrentThread生成的java层线程,和delete 掉jniEnv。
  • jvm_attach_process函数,生成好JNIEnv和java方法句柄onRecvVideoDataM。在视频流线程开始的时候调用,这样就是把c/c++线程attach到新生成的java线程。
  • jvm_detach_process()函数,是在视频流线程结束的时候调用,相当于关闭java线程,delete掉JNIEnv。

 

还有一个问题,如果在系统自己生成的JNI函数里回调java,那么函数传进来的JNIEnv ,与之相对应的是调用native方法的java线程。所以在java层会阻塞。

JNICALL
Java_com_test_server_start(JNIEnv *env, jobject object) {
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android NDK中,我们可以使用JNI(Java Native Interface)来实现C/C++代码和Java代码的相互调用。 下面是一个简单的例子,展示了如何在NDK中调用Java方法。 首先,在Java中创建一个类,并在其中声明一个需要被C/C++回调方法: ```java public class MyCallback { public void processData(byte[] data) { // 处理数据的逻辑 } } ``` 然后,在C/C++代码中,我们需要使用JNI来获取Java的MyCallback对象,并调用其processData方法。具体步骤如下: 1. 首先,需要在C/C++代码中引入JNI头文件: ```c++ #include <jni.h> ``` 2. 获取Java的MyCallback对象: ```c++ JNIEnv* env; JavaVM* jvm; // 获取当前线程的JNIEnv指针 jvm->AttachCurrentThread(&env, NULL); // 获取MyCallback类 jclass myCallbackClass = env->FindClass("com/example/MyCallback"); // 获取MyCallback对象 jmethodID constructor = env->GetMethodID(myCallbackClass, "<init>", "()V"); jobject myCallbackObj = env->NewObject(myCallbackClass, constructor); ``` 3. 调用MyCallback对象的processData方法: ```c++ // 获取processData方法ID jmethodID processDataMethod = env->GetMethodID(myCallbackClass, "processData", "([B)V"); // 构造byte[]对象 jbyteArray data = env->NewByteArray(size); env->SetByteArrayRegion(data, 0, size, (jbyte*)buf); // 调用processData方法 env->CallVoidMethod(myCallbackObj, processDataMethod, data); ``` 最后,记得在C/C++代码中释放JNI相关资源: ```c++ jvm->DetachCurrentThread(); env->DeleteLocalRef(myCallbackClass); env->DeleteLocalRef(myCallbackObj); env->DeleteLocalRef(data); ``` 以上就是在NDK中实现线程回调Java方法的基本步骤。需要注意的是,在调用Java方法时,需要使用JNIEnv指针。此外,如果在多线程环境下操作JNI,需要使用jvm->AttachCurrentThread()方法获取当前线程的JNIEnv指针。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值