注: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
分两种情况
- 线程中包含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;
- 线程中不包含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);
}