从Java到C++:异步

从Java到C++系列目录

前言

摘要

本文主要通过一个简化的MediaPlayer实现,来讲解JNI的异步引发的几个问题:

异步线程如何获取JNIEnv?

Java和C++的MediaPlayer对象如何实现一一映射?

异步任务回调如何避免内存泄漏?

代码

示例代码:简化的MediaPlayer

测试代码:MediaPlayerTest

简化版MediaPlayer

类图

mediaplayer类图.png

注意事项:

左边是Java层的MediaPlayer。右边是C++层的MediaPlayer。两者通过android_media_MediaPlayer.cpp里定义的JNI方法,进行交互。

流程图

mediaplayer流程图.png

注意事项:

  1. native_setup是在MediaPlayer的构造方法里触发的。
  2. native_finalize是在MediaPlayer的析构函数finalize里触发的。finalize在MediaPlayer被gc时触发。
  3. prepareAsync是一个异步调用。prepareAsync的异步调用完成后,会进行notify,最终调用Java层的postEventFromNative,通知Java层结果。
  4. 在MediaPlayer的设计里,Java层和C++层的MediaPlayer对象是一一对应的。

异步带来的问题

异步线程如何获取JNIEnv?

JNI方法基本都是通过JNIEnv的指针来调用。但JNIEnv是线程独有的,无法在线程之间共享JNIEnv。那么C++层异步任务执行完成后,如何获取JNIEnv

解决方案:

通过JavaVM获取对应的JNIEnv:

  1. JNI_OnLoad里获取JavaVM,保存起来
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    AndroidRuntime::setJavaVM(vm);
}
  1. 在回调线程里,使用JavaVM获取JNIEnv
JNIEnv *env;
JavaVM *vm = AndroidRuntime::getJavaVM();
if (vm->AttachCurrentThread(&env, NULL) != JNI_OK) {
    return;
}
...

//使用完,记得Detach
vm->DetachCurrentThread();

Java和C++的MediaPlayer对象如何实现一一映射?

解决方案:

在Java对象里,保存指向C++对象的指针:

  1. 在Java中,定义一个long类型变量,保存C++对象的指针:
public class MediaPlayer {
    @CallByNative
    private long mNativeContext;
}
  1. 在C++层MediaPlay创建时,通过JNI方法,设置C++对象的指针给Java的mNativeContext变量:
static void setMediaPlayer(JNIEnv *env, jobject thiz, MediaPlayer *player) {
    std::unique_lock<std::mutex> lock(sLock);
    //指针要强转为jlong
    env->SetLongField(thiz, javaMediaPlayer.native_context, reinterpret_cast<jlong>(player));
}

void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_thiz) {
    MediaPlayer *player = new MediaPlayer();
    setMediaPlayer(env, thiz, player);
}
  1. 如何使用mNativeContext?有两点要注意:
  • mNativeContext对于Java而已,只是一个long类型变量,没有特殊的意义。
  • mNativeContext的使用场景,基本是在C++层。比如,通过native方法传递的Java的MediaPlayer对象,获取mNativeContext,强转为对应的C++对象的指针。然后通过该指针,调用C++层的MediaPlayer对象的方法。
static MediaPlayer *getMediaPlayer(JNIEnv *env, jobject thiz) {
    std::unique_lock<std::mutex> lock(sLock);
    return reinterpret_cast<MediaPlayer *>(env->GetLongField(thiz, javaMediaPlayer.native_context));
}
  1. 销毁对象
    Java代码:
@Override
protected void finalize() throws Throwable {
    super.finalize();
    native_finalize();
}

C++代码:

void android_media_MediaPlayer_finalize(JNIEnv *env, jobject thiz) {
    MediaPlayer *player = getMediaPlayer(env, thiz);
    if (player) delete player;
    setMediaPlayer(env, thiz, 0);
}

思考:

如果在native方法里,使用一个静态Map来保存这种映射关系是否可行?

  • android_media_MediaPlayer_native_setup里,put(javaMediaPlayer,cppMediaPlayer)进去
  • android_media_MediaPlayer_finalize里,remove(javaMediaPlayer)出来

不可行。如果是以Java的MediaPlayer对象的全局引用为key,由于native方法引用的对象,是会被视为GC Roots的。这会导致Java层的finalize一直无法触发,引发内存泄漏。

异步任务回调如何避免内存泄漏?

prepareAsync是一个耗时的异步任务。任务完成后,C++必须通过定位Java层对应的MediaPlayer对象,来通知任务处理结果。

拍脑袋的想法,可能是在C++的MediaPlayer里,保存一个Java的MediaPlayer对象的全局引用。在任务完成时,通过全局引用调用回调处理方法。

但是和前一小节的Map问题类似,全局引用引用的对象,是会被视为GC Roots的。而全局引用在手动释放它之前,都会一直存在,所以容易造成Java对象无法正常被垃圾回收。

解决方案:

使用弱引用。prepareAsync异步任务完成后,C++通过弱引用,最终定位到Java的MediaPlayer对象:

  1. 在Java层使用弱引用包裹回调接口,传递进native层:
public class MediaPlayer {
    public MediaPlayer() {
        native_setup(new WeakReference<>(this));
    }
    
    private native void native_setup(Object weak_this);
}
  1. 引入一个JNIMediaPlayerListener类,将弱引用的全局引用,保存在其成员变量中:
    C++代码:
class JNIMediaPlayerListener : public MediaPlayerListener {
public:
    JNIMediaPlayerListener(JNIEnv *env, jobject thiz, jobject weak_thiz);

    ~JNIMediaPlayerListener();

    virtual void notify(int msg);

private:
    jclass mClass;
    jobject mObject;
};

JNIMediaPlayerListener::JNIMediaPlayerListener(JNIEnv *env, jobject thiz, jobject weak_thiz) {
    jclass c = env->GetObjectClass(thiz);
    if (c == nullptr) {
        const char *msg = "Can't find com/example/java2cpp/MediaPlayer";
        env->FatalError(msg);
    }

    mClass = (jclass) env->NewGlobalRef(c);
    mObject = env->NewGlobalRef(weak_thiz);
}

void android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_thiz) {
    MediaPlayer *player = new MediaPlayer();
    JNIMediaPlayerListener *listener = new JNIMediaPlayerListener(env, thiz, weak_thiz);
    player->setListener(listener);
    setMediaPlayer(env, thiz, player);
}
  1. 在C++层的MediaPlayer中,新增mListener成员变量:
class MediaPlayerListener {
public:
    virtual void notify(int msg) = 0;
};

class MediaPlayer {
private:
    MediaPlayerListener *mListener;
};
  1. 最后,在prepareAsync完成后,调用mListener的notify方法,通知Java层任务结果:
void JNIMediaPlayerListener::notify(int msg) {
    JNIEnv *env;
    JavaVM *vm = AndroidRuntime::getJavaVM();
    if (vm->AttachCurrentThread(&env, NULL) != JNI_OK) {
        return;
    }
    env->CallStaticVoidMethod(mClass, javaMediaPlayer.post_event, mObject, msg);
    //使用完,记得Detach
    vm->DetachCurrentThread();
}
  1. Java层的postEventFromNative最终会被调用:
public class MediaPlayer {
    @CallByNative
    private static void postEventFromNative(Object mediaplayer_weak_ref, int msg) {
        MediaPlayer mp = (MediaPlayer) ((WeakReference) mediaplayer_weak_ref).get();
        if (mp == null) return;
        if (mp.mOnPreparedListener != null) {
            mp.mOnPreparedListener.onPrepared(msg);
        }
    }
}

参考资料

Android源码:MediaPlayer.javamediaplayer.cpp
android_media_MediaPlayer.cpp

猜你喜欢

转载自blog.csdn.net/qq_34356130/article/details/123600277