欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JNI线程相关

程序员文章站 2022-03-29 17:45:57
注:Android develop中给的的编码建议是:尽可能避免在使用受管理编程语言编写的代码与使用 C++ 编写的代码之间进行异步通信。这样可使 JNI 接口更易于维护。通常,您可以采用与编写界面相同的编程语言保持异步更新,以简化异步界面更新。例如,最好使用 Java 编程语言在两个线程之间进行回调(其中一个线程发出阻塞 C++ 调用,然后在阻塞调用完成时通知界面线程),而不是通过 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 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();
    }

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

  • 局部引用
    传递给原生方法的每个参数,以及 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

本文地址:https://blog.csdn.net/yizhongliu/article/details/107508285