前言
摘要
本文主要通过一个简化的MediaPlayer实现,来讲解JNI的异步引发的几个问题:
Java和C++的MediaPlayer对象如何实现一一映射?
代码
示例代码:简化的MediaPlayer
测试代码:MediaPlayerTest
简化版MediaPlayer
类图
注意事项:
左边是Java层的MediaPlayer。右边是C++层的MediaPlayer。两者通过android_media_MediaPlayer.cpp
里定义的JNI方法,进行交互。
流程图
注意事项:
native_setup
是在MediaPlayer的构造方法里触发的。native_finalize
是在MediaPlayer的析构函数finalize
里触发的。finalize
在MediaPlayer被gc时触发。prepareAsync
是一个异步调用。prepareAsync
的异步调用完成后,会进行notify
,最终调用Java层的postEventFromNative
,通知Java层结果。- 在MediaPlayer的设计里,Java层和C++层的MediaPlayer对象是一一对应的。
异步带来的问题
异步线程如何获取JNIEnv?
JNI方法基本都是通过JNIEnv
的指针来调用。但JNIEnv
是线程独有的,无法在线程之间共享JNIEnv。那么C++层异步任务执行完成后,如何获取JNIEnv
?
解决方案:
通过JavaVM
获取对应的JNIEnv
:
- 在
JNI_OnLoad
里获取JavaVM,保存起来
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
AndroidRuntime::setJavaVM(vm);
}
- 在回调线程里,使用
JavaVM
获取JNIEnv
:
JNIEnv *env;
JavaVM *vm = AndroidRuntime::getJavaVM();
if (vm->AttachCurrentThread(&env, NULL) != JNI_OK) {
return;
}
...
//使用完,记得Detach
vm->DetachCurrentThread();
Java和C++的MediaPlayer对象如何实现一一映射?
解决方案:
在Java对象里,保存指向C++对象的指针:
- 在Java中,定义一个long类型变量,保存C++对象的指针:
public class MediaPlayer {
@CallByNative
private long mNativeContext;
}
- 在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);
}
- 如何使用
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));
}
- 销毁对象
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对象:
- 在Java层使用弱引用包裹回调接口,传递进native层:
public class MediaPlayer {
public MediaPlayer() {
native_setup(new WeakReference<>(this));
}
private native void native_setup(Object weak_this);
}
- 引入一个
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);
}
- 在C++层的MediaPlayer中,新增
mListener
成员变量:
class MediaPlayerListener {
public:
virtual void notify(int msg) = 0;
};
class MediaPlayer {
private:
MediaPlayerListener *mListener;
};
- 最后,在
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();
}
- 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.java、mediaplayer.cpp
、android_media_MediaPlayer.cpp