通常设备层有数据或事件要通知到应用层是通过回调来完成的,可以采用的方式是直接调用java层类静态函数,或者调用java层某个对象的普通函数,
涉及几个问题:
1,内存泄露,引用问题
2,多线程问题
首先研究一下Camera的实现,首先打开Camera返回一个Camera对象,setPreviewCallback传入了一个callback,这个callback没有传入jni层,只是在Camera对象内部保存下来了,之后会用到。Camera的构造函数中会调用native层的初始化函数native_setup,将Camera对象自己的WeakReference作为参数传给jni层。这里要考虑一下为什么不直接传入Camera对象呢,jni层有弱全局引用同样可以达到类似效果。
当Camera底层有数据要回调到Java层时,由于jni层保留了Camera对象的WeakReference,所以jni层调用java层的Camera类的静态函数postEventFromNative,传入Camera对象的WeakReference以及其他参数,看如下实现:
private static void postEventFromNative(Object camera_ref, int what, ...) {
Camera c = (Camera)((WeakReference) camera_ref).get();
if (c == null)
return;
if (c.mEventHandler != null) {
Message m = c.mEventHandler.obtainMessage(what, arg1, arg2, obj);
c.mEventHandler.sendMessage(m);
}
}
首先看看WeakReference失效了没有,如果没有再post到统一线程去回调给java。
接下来再看看jni层,android_hardware_Camera.cpp,先看native_Setup的实现,
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName) {
sp<Camera> camera = Camera::connect(cameraId, clientName,
Camera::USE_CALLING_UID);
jclass clazz = env->GetObjectClass(thiz);
// We use a weak reference so the Camera object can be garbage collected.
// The reference is only used as a proxy for callbacks.
sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);
context->incStrong((void*)android_hardware_Camera_native_setup);
camera->setListener(context);
// save context in opaque field
env->SetLongField(thiz, fields.context, (jlong)context.get());
return NO_ERROR;
}
这里先创建一个Jni层的Camera对象,然后将Java层Camera对象的WeakReference,Java层Camera的class,Jni层的Camera对象打包成JNICameraContext对象,然后设置到Camera的Java层对象中保存为id,之后Java层再调到JNI层时带上这个id,在JNI层即可找到之前的JNICameraContext。
看看JNICameraContext的构造函数,这里给Java层Camera对象的WeakReference在Jni层用NewGlobalRef保存下来了。
JNICameraContext::JNICameraContext(JNIEnv* env, jobject weak_this, jclass clazz, const sp<Camera>& camera) {
mCameraJObjectWeak = env->NewGlobalRef(weak_this);
mCameraJClass = (jclass)env->NewGlobalRef(clazz);
mCamera = camera;
jclass faceClazz = env->FindClass("android/hardware/Camera$Face");
mFaceClass = (jclass) env->NewGlobalRef(faceClazz);
jclass rectClazz = env->FindClass("android/graphics/Rect");
mRectClass = (jclass) env->NewGlobalRef(rectClazz);
jclass pointClazz = env->FindClass("android/graphics/Point");
mPointClass = (jclass) env->NewGlobalRef(pointClazz);
mManualBufferMode = false;
mManualCameraCallbackSet = false;
}
再看看有事件通知时是如何回调的,
void JNICameraContext::notify(int32_t msgType, int32_t ext1, int32_t ext2) {
if (mCameraJObjectWeak == NULL) {
ALOGW("callback on dead camera object");
return;
}
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->CallStaticVoidMethod(mCameraJClass, fields.post_event,
mCameraJObjectWeak, msgType, ext1, ext2, NULL);
}
这里先获取当前线程的JNIEnv,然后调Java层的函数,传入mCameraJObjectWeak,这对应的是Java层Camera对象的WeakReference。JNIEnv保存在ThreadLocal中。如果为null就通过AttachCurrentThread设置。getJavaVM返回的是AndroidRuntime的静态成员mJavaVM,这应该是虚拟机启动时就注册好的。
/*
* Get the JNIEnv pointer for this thread.
*
* Returns NULL if the slot wasn't allocated or populated.
*/
/*static*/ JNIEnv* AndroidRuntime::getJNIEnv()
{
JNIEnv* env;
JavaVM* vm = AndroidRuntime::getJavaVM();
assert(vm != NULL);
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
return NULL;
return env;
}
/*
* Makes the current thread visible to the VM.
*
* The JNIEnv pointer returned is only valid for the current thread, and
* thus must be tucked into thread-local storage.
*/
static int javaAttachThread(const char* threadName, JNIEnv** pEnv) {
JavaVM* vm = AndroidRuntime::getJavaVM();
args.version = JNI_VERSION_1_4;
args.name = (char*) threadName;
args.group = NULL;
result = vm->AttachCurrentThread(pEnv, (void*) &args);
return result;
}
/*
* Detach the current thread from the set visible to the VM.
*/
static int javaDetachThread(void) {
JavaVM* vm = AndroidRuntime::getJavaVM();
result = vm->DetachCurrentThread();
return result;
}
总结一下:
这套架构适用于Java层和JNI层非单例的情况,即Java层对象要和Jni层对象一一对应,Java层保存了JNI层对象的指针,目的是Java层调JNI层时能找到Jni层的对象。Jni层同样保存了Java层对象的对象WeakReference,目的是Jni调Java层时能找到Java层对象。且由于有WeakReference隔离所以不会内存泄露。