從Camera源碼看如何從Jni回調到Java層

通常設備層有數據或事件要通知到應用層是通過回調來完成的,可以採用的方式是直接調用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隔離所以不會內存泄露。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章