使用FFMpeg 模仿Android MediaPlayer

JNI线程相关

2020-07-22  本文已影响0人  llm_5243

注:Android develop中给的的编码建议是:

JNIEnv 与多线程

之前文章提到过JNIEnv是线程相关的,即在每一个线程中都有一个JNIEnv指针,每个JNIEnv都是线程专有的,其他线程不能使用本线程中的JNIEnv.

一种比较常见的应用场景是:在native 层创建了线程,线程执行完后想将结果返回给java层,这时线程是不能用jni函数参数中的JNIEnv的,因为参数中的JNIEnv属于不同的线程.

在线程中获取或创建JNIEnv

分两种情况

  1. 线程中包含JNIEnv
    如果一段代码无法通过其他方法获取自己的 JNIEnv,您应该共享相应 JavaVM,然后使用 GetEnv 发现线程的 JNIEnv. JNI_OnLoad就是通过GetEnv去获取JNIEnv的
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = NULL;
    jint result = -1;

    javaVM = vm;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);
   //省略 ...
    result = JNI_VERSION_1_4;
   
    bail:
    return result;
  1. 线程中不包含JNIEnv
    所有线程都是 Linux 线程,由内核调度。线程通常从受管理代码启动(使用 Thread.start()),但也可以在其他位置创建,然后附加到 JavaVM。例如,可以使用 AttachCurrentThread() 或 AttachCurrentThreadAsDaemon() 函数附加.通过 pthread_create() 或 std::thread 启动的线程。在附加之前,线程不包含任何 JNIEnv,也无法调用 JNI.在已附加的线程上调用 AttachCurrentThread() 属于空操作。
    通过 JNI 附加的线程在退出之前必须调用 DetachCurrentThread()。如果直接对此进行编码会很棘手.
 //先通过GetEnv去获取当前线程是否有JNIEnv, 如果没有再通过
 //AttachCurrentThread将当前线程附加到 JavaVM
 int status = javaVM->GetEnv((void**)&env, JNI_VERSION_1_4);
    if (status < 0) {
        javaVM->AttachCurrentThread(&env, NULL);
        ALOGE("AttachCurrentThread");
    }
    ...
    if (status < 0) {
        javaVM->DetachCurrentThread();
    }

局部引用,全局引用和弱全局引用

关于全局引用,Android MediaPlayer中有一个应用场景:

MediaPlayer中有好几个回调如onPrepared,onError等,都是native层回调java的postEventFromNative函数将消息传递上来的. natvie回调java需要获取到MediaPlayer的object,这个object是java层的MediaPlayer通过jni接口传递给native层的,属于局部引用,而native层发送消息可能是在不同的线程,所以必须要将object变成全局的引用.下面看下代码的实现:

frameworks/base/media/java/android/media/MediaPlayer.java

public MediaPlayer() {
       ...
        /* Native setup requires a weak reference to our object.
         * It's easier to create it here than in C++.
         */
        native_setup(new WeakReference<MediaPlayer>(this));
      ...
    }

在MediaPlayer的构造函数里,调用了native层的函数native_setup,将自身的object弱引用传给native层

frameworks/base/media/jni/android_media_MediaPlayer.cpp

static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
   ...
    // create new listener and give it to MediaPlayer
    sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
    mp->setListener(listener);
...
}

jni函数中,创建了JNIMediaPlayerListener,将weak_this,即java层MediaPlayer的object传给JNIMediaPlayerListener.jni就是通过JNIMediaPlayerListener回调java的.再来看下JNIMediaPlayerListener

JNIMediaPlayerListener::JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
{

    // Hold onto the MediaPlayer class for use in calling the static method
    // that posts events to the application thread.
    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        ALOGE("Can't find android/media/MediaPlayer");
        jniThrowException(env, "java/lang/Exception", NULL);
        return;
    }
    mClass = (jclass)env->NewGlobalRef(clazz);

    // We use a weak reference so the MediaPlayer object can be garbage collected.
    // The reference is only used as a proxy for callbacks.
    mObject  = env->NewGlobalRef(weak_thiz);
}

可以看到,在构造函数中调用了mObject = env->NewGlobalRef(weak_thiz);创建了对MediaPlayer object的全局引用.u全局引用必须要主动地去释放它,可以猜测到释放的地方是在JNIMediaPlayerListener的析构函数

JNIMediaPlayerListener::~JNIMediaPlayerListener()
{
    // remove global references
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    env->DeleteGlobalRef(mObject);
    env->DeleteGlobalRef(mClass);
}

参考
https://blog.csdn.net/xyang81/article/details/44657385

上一篇 下一篇

猜你喜欢

热点阅读