JNI线程相关

时间:2022-07-22
本文章向大家介绍JNI线程相关,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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

  • 尽可能避免在使用受管理编程语言编写的代码与使用 C++ 编写的代码之间进行异步通信。这样可使 JNI 接口更易于维护。通常,您可以采用与编写界面相同的编程语言保持异步更新,以简化异步界面更新。例如,最好使用 Java 编程语言在两个线程之间进行回调(其中一个线程发出阻塞 C++ 调用,然后在阻塞调用完成时通知界面线程),而不是通过 JNI 从使用 Java 代码的界面线程调用 C++ 函数。
  • 尽可能减少需要接触 JNI 或被 JNI 接触的线程数。如果您确实需要使用 Java 和 C++ 这两种语言的线程池,请尽量保持在池所有者之间(而不是各个工作器线程之间)进行 JNI 通信。

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 failedn");
        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();
    }

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

  • 局部引用 传递给原生方法的每个参数,以及 JNI 函数返回的几乎每个对象都属于“局部引用”。例如通过NewLocalRef和各种JNI接口创建(FindClass、NewObject、GetObjectClass和NewCharArray等)。会阻止GC回收所引用的对象,不能在本地函数中跨函数使用,不能跨线前使用。 函数返回后局部引用所引用的对象会被JVM自动释放. 通过NewLocalRef创建的局部引用,如果不通过函数返回,需要调用DeleteLocalRef释放。
  • 全局引用 调用NewGlobalRef基于局部引用创建,会阻GC回收所引用的对象。可以跨方法、跨线程使用。JVM不会自动释放,必须调用DeleteGlobalRef手动释放
  • 弱全局引用 调用NewWeakGlobalRef基于局部引用或全局引用创建,不会阻止GC回收所引用的对象,可以跨方法、跨线程使用。引用不会自动释放,在JVM认为应该回收它的时候(比如内存紧张的时候)进行回收而被释放。或调用DeleteWeakGlobalRef手动释放。

关于全局引用,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