从Camera源码看如何从Jni回调到Java层

本文探讨了Android Camera组件中,如何从JNI层回调到Java层的过程。通过分析源码,指出Camera在初始化时如何保存WeakReference,并在JNI层触发回调时,检查WeakReference的有效性,确保数据正确传递。同时,文章揭示了JNI层如何通过保存的Java对象引用避免内存泄露,以及在多线程环境下回调的安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

通常设备层有数据或事件要通知到应用层是通过回调来完成的,可以采用的方式是直接调用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隔离所以不会内存泄露。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值