Android - JNI

JNI是JAVA语言和C/C++(native)语言之间互相调用的接口。


如何使用JNI

JAVA调用C/C++

使用JNI还是很方便,主要分两步:

1. 加载C/C++生成的动态库

如 System.loadLibrary("media_jni"),就是要加载libmedia_jni.so动态库。

2. 声明native函数

如 private static native final void native_init(),这里面有个修饰符是native,说明该函数是native库里面的函数。


C/C++调用JAVA

我们以MediaRecorder的一个应用为例,录像的文件大小可以设置,在录制的过程中,当达到设置的大小时,C++部分就会通知Java应用程序,这个过程就是C++调用Java函数。这个回调Java主要的处理环节在android_media_MediaRecorder.cpp的JNIMediaRecorderListener:: notify函数:

void JNIMediaRecorderListener::notify(int msg, int ext1, int ext2)
{
    LOGV("JNIMediaRecorderListener::notify");

    JNIEnv *env = AndroidRuntime::getJNIEnv();
    env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, 0);
}
该函数完成两个任务
1. 获得JNI的环境参数JNIEnv;
2. 通过CallStaticVoidMethod函数调用Java函数,这个函数就是mClass的fields.post_event方法,后面的其他参数为Java的post_event函数的参数;

这样就可以调用Java某个类的方法了。

可以看出,在这里面最重要的一个中间传递者就是JNIEnv,它可以获得Java层类对象以及类对象的方法和成员,对于更详细介绍JNIEnv,请链接到JNIEnv小节。下面是android_media_MediaRecorder.cpp的获得类和类方法和成员的代码:

static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
    jclass clazz;

    clazz = env->FindClass("android/media/MediaRecorder");
    if (clazz == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaRecorder");
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    if (fields.context == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaRecorder.mNativeContext");
        return;
    }

    fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
    if (fields.surface == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaRecorder.mSurface");
        return;
    }

    jclass surface = env->FindClass("android/view/Surface");
    if (surface == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/view/Surface");
        return;
    }

    fields.surface_native = env->GetFieldID(surface, ANDROID_VIEW_SURFACE_JNI_ID, "I");
    if (fields.surface_native == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find Surface.mSurface");
        return;
    }

    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "MediaRecorder.postEventFromNative");
        return;
    }
}

1. 通过FindClass获得了android/media/MediaRecorder类;

2. 通过GetFieldID获得了android/media/MediaRecorder类的mNativeContext和mSurface成员;

3. 通过FindClass获得了android/view/Surface类;

4. 通过GetFieldID获得了android/view/Surface类的mNativeSurface成员;

5. 通过GetStaticMethodID获得了postEventFromNative方法;

Java native 函数和C/C++函数的挂钩

虽然,JNI用起来很简单,如何才能将Java声明的native函数和实际的C/C++函数挂钩呢?通过查询,有两种办法,Android常用的是动态注册,还有一个静态注册方法。


动态注册

我们以android_media_MediaRecorder.cpp为例,MediaRecoder.java声明了“public native void start()”函数,该start函数要和android_media_MediaRecorder.cpp文件的android_media_MediaRecorder_start函数挂钩,他俩是如何联系到一起的呢。

通过查阅资料,当Java加载动态库后,它会执行该库的JNI_OnLoad函数,该函数包含如下几行代码:

    if (register_android_media_MediaRecorder(env) < 0) {
        LOGE("ERROR: MediaRecorder native registration failed\n");
        goto bail;
    }

通过函数名字是注册MediaRecorder,register_android_media_MediaRecorder(env) 定义如下

int register_android_media_MediaRecorder(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "android/media/MediaRecorder", gMethods, NELEM(gMethods));
}
从中,有两个参量很重要,一个是 "android/media/MediaRecorder",它是Java程序的相应的类;一个是gMethods结构体数组,这个数组包含很多如下格式的结构体 {"start", "()V", (void *)android_media_MediaRecorder_start},从表面意思就是将Java的"android/media/MediaRecorder"类的start函数和android_media_MediaRecorder_start函数挂钩,这样就实现了注册。AndroidRuntime::registerNativeMethods定义如下

int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

/*
 * Register native JNI-callable methods.
 *
 * "className" looks like "java/lang/String".
 */
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    LOGV("Registering %s natives\n", className);
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'\n", className);
        return -1;
    }

    int result = 0;
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'\n", className);
        result = -1;
    }

    (*env)->DeleteLocalRef(env, clazz);
    return result;
}

/*
 * Register one or more native functions in one class.
 *
 * This can be called multiple times on the same method, allowing the
 * caller to redefine the method implementation at will.
 */
static jint RegisterNatives(JNIEnv* env, jclass jclazz,
    const JNINativeMethod* methods, jint nMethods)
{
    JNI_ENTER();

    ClassObject* clazz = (ClassObject*) dvmDecodeIndirectRef(env, jclazz);
    jint retval = JNI_OK;
    int i;

    if (gDvm.verboseJni) {
        LOGI("[Registering JNI native methods for class %s]\n",
            clazz->descriptor);
    }

    for (i = 0; i < nMethods; i++) {
        if (!dvmRegisterJNIMethod(clazz, methods[i].name,
                methods[i].signature, methods[i].fnPtr))
        {
            retval = JNI_ERR;
        }
    }

    JNI_EXIT();
    return retval;
}

以上的步骤基本如下:

1. 通过传入的Class字符串查找Java中是否有对应的类;

2. 通过Java虚拟机提供的注册函数接口,将gMethods数组的函数对注册到虚拟机中;

这样就完成了动态注册。


静态注册

静态注册方法是通过函数名字进行挂钩的方法,如Java start函数,那么它就到对应的动态库中寻找名字为“android_media_MediaRecorder_start”的函数,其中“android_media_MediaRecordert”表示对应的Java类名,_start表示函数的名字,这样做的方法有以下几点弊端,同时也是动态注册的优点:

1. C/C++定义的函数名字必须遵循这样的规定,否则两个函数无法挂钩;

2. 第一次调用函数时,必须到动态库中搜索这个函数,会影响速度,以后就会将对应的函数地址记录下来,所以第二次及以后使用该函数就和动态注册效率一致;


JNIEnv

在上面两节都用到了JNIEnv,看来通过它可以获得Java层有什么类对象,以及类对象的成员和方法;可以将Java层的native函数和C/C++函数挂钩,那么这个JNIEnv是如何来的呢?

在android_media_MediaPlayer.cpp中的JNI_OnLoad函数:

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    if (register_android_media_MediaPlayer(env) < 0) {
        LOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }
1. JNI_OnLoad第一个参数是JavaVM,这表示和java虚拟机有关的参量,一个进程只有一个这个参数;

2. 通过vm->GetEnv就可以获得JNIEnv参量,通过JNIEnv参量就可以让Java获得C/C++对应的函数;让C/C++获得Java相应的方法和成员变量;


JNIEnv就是JNI技术的关键。JNIEnv再向下执行,就是调用Java虚拟提供的功能接口了,我们就不作分析了,我只要懂得怎么使用JNI技术即可,我们要明白,我们既可以在Java层获得C/C++程序,也可以在C/C++层调用Java程序。


引用计数

JNI共提供了三种引用计数,和C++的引用计数还有很大的不同。


Local Reference  

本地引用,jni层函数使用的非全局引用对象都是local reference,包括函数调用时传入的jobject和在JNI层函数中创建的jobject。

当所在函数退出时,这些jobject才可能被垃圾回收。

上面传达了两层意思:

1. 函数退出时,本地引用不一定马上回收;

2. 函数没退出时,本地引用不会被回收;

所以,当出现类似下面的情况时,

For(int i = 0; i < 100; i++){
    Jstring pathStr = mEnv->NewStringUTF(path);
    //do something
}

在函数退出前,会出现100个pathStr,所以内存可能很快被消耗完,那怎么办呢?

如果在每个循环里面调用env->DeleteLocalRef(pathStr),那么当执行完一个循环,pathStr就会被回收。

所以,DeleteLocalRef是本地引用一个很重要的操作。


Global Reference

如下面的一段JNI代码:

Android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz,jstring path, jstring mimeType, jobject client){
。。。。。。;
//保存Java层换入的jobject对象,代表MediaScanner对象
Save_this = thiz;
。。。。。。;
Return;
}

这里面是有问题的,因为JNI里面Save_this = thiz这句话是不会改变引用计数的,所以,如果其他函数有调用Save_this的地方,可能对应的thiz对象已经被释放了。那怎么办呢?

JNI提供了全局引用,全局引用对象如不主动释放,它永远不会被垃圾回收。

所以如

Save_this = (jclass)env->NewGlobalRef(thiz);

将Save_this设为全局引用,那么对应的thiz对象是不会被释放的。

只有当调用env->DeleteGlobalRef(Save_this)时,对应的thiz对象才有可能被回收,否则永远不会被释放。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值