1 minute read

JNI,即Java Native Interface,Java本地调用。

1. 概述

通过JNI可以实现:

  • Java程序函数可以调用Natvie语言(C/C++)写的函数
  • Natvie程序函数可以调用Java层的函数

2. MediaScanner示例

使用Android Xref提供的 Android 9.0.0_r3的源码

2.1. Java层的MediaScanner

完成两件事:

  1. 加载JNI库
  2. Java的native函数
// frameworks/base/media/java/android/media/MediaScanner.java
public class MediaScanner implements AutoCloseable {
    static {
        System.loadLibrary("media_jni");  //加载对应的JNI库,media_jni是库名,在实际加载动态库的时候会拓展成libmedia_jni.so
        native_init();  //调用函数
    }

    private final static String TAG = "MediaScanner";
...
    private native void processDirectory(String path, MediaScannerClient client);
    private native boolean processFile(String path, String mimeType, MediaScannerClient client);
    private native void setLocale(String locale);

    public native byte[] extractAlbumArt(FileDescriptor fd);
    private static native final void native_init();
    private native final void native_setup();
    private native final void native_finalize();

动态库是运行时加载的库。如果Java要调用native函数,必须通过一个位于JNI层的动态库实现。通常是在类的static语句中加载,调用System.loadLibrary方法就可以加载。

函数名前有Java的关键字native的函数表示将由JNI层实现。

因而Java层只需要两项工作:加载对应的JNI库,和声明由关键字native修饰的函数

2.2. JNI层的MediaScanner

JNI对应的文件是frameworks/base/media/jni/android_media_MediaScanner.cpp

// frameworks/base/media/jni/android_media_MediaScanner.cpp
// This function gets a field ID, which in turn causes class initialization.
// It is called from a static block in MediaScanner, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
    ALOGV("native_init");
    jclass clazz = env->FindClass(kClassMediaScanner);
    if (clazz == NULL) {
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }
}
...

java层的native_init函数对应android_media_MediaScanner_native_init。通过文件路径来命名,观察两个文件的路径:

  • frameworks/base/media/java/android/media/MediaScanner.java
  • frameworks/base/media/jni/android_media_MediaScanner.cpp

2.2.1. 注册JNI函数

java层的MediaScanner.java函数native_init位于android.media包中,全路径名是:android/media/MediaScanner.java,对应JNI层函数的名字。JNI层将Java函数名称(包含包名)中的.转换成_,通过这种方式,native_init对应JNI的函数。

JNI函数注册的意思是将java层的native函数和JNI层对应的实现函数关联起来。注册有两种方式:静态方法和动态注册

1.静态注册

根据函数名来找对应的JNI函数。

(1) 编写Java代码,编译生成.class文件

(2) 使用Java的工具命令javah -o output packagename.classname,生成一个output.h的JNI头文件,里面声明了对应的JNI函数,只要实现里面的函数即可。

2.动态注册

因为Java native函数和JNI函数是一一对应的,所以存在一种JNINativeMethod的结构记录这种一一对应的关系。

例如frameworks/base/media/jni/android_media_MediaScanner.cpp文件中:

...
static const JNINativeMethod gMethods[] = {
 ......
    {
        "native_setup",
        "()V",
        (void *)android_media_MediaScanner_native_setup
    },

    {
        "native_finalize",
        "()V",
        (void *)android_media_MediaScanner_native_finalize
    },
};
//注册上面的数组
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaScanner(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                kClassMediaScanner, gMethods, NELEM(gMethods));
}

当Java层通过System.loadLibrary加载完JNI动态库后,会查找该库中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) {
        ALOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

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

    if (register_android_media_MediaScanner(env) < 0) {   //此处开始动态注册
        ALOGE("ERROR: MediaScanner native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaMetadataRetriever(env) < 0) {
        ALOGE("ERROR: MediaMetadataRetriever native registration failed\n");
        goto bail;
    }
......

    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
}

Leave a comment