Android JNI

来源:互联网 发布:关于linux 的书籍 编辑:程序博客网 时间:2024/06/07 03:49

什么是JNI,Java Native Interface ,Java 本地调用。Java 虽然具有跨平台的特性,但是Java和具体的平台之间的隔离是通过JNI层来实现的,Android 中 Java 通过 JNI 层调用 Linux 中的接口来实现对应的功能。JNI 层一般是由 C C++ 文件编写。


Java 程序
1、加载对应的JNI库,同行的做法是放在类的 static 语句中加载
2、声明 native 函数,表示这个函数在 JNI 层实现
public class JNIDemo{        static {                /* load */                System.loadLibrary("native");/* libnative.so */        }        public native void hello();        public native int add(int a, int b);        public native String str(String str);        public native int [] array(int [] a);        public static void main(String args[]){                JNIDemo d = new JNIDemo();                /* hello */                d.hello();                /* int */                System.out.println(d.add(1,2));                /* string */                System.out.println(d.str("hello world"));                /* array */                int [] a = {1,2,3};                int [] b = d.array(a);                for (int i = 0; i < b.length; i++)                        System.out.println(b[i]);        }}


JNI C 程序
1、实现一个 JNI_OnLoad 函数,JNI_OnLoad 函数将在 java System.loadLibrary 后在该库中寻找并调用 JNI_OnLoad
2、一般在JNI_OnLoad 函数中注册一个 JNINativeMethod 类型的数组来表示 Java 与 JNI 中函数的映射关系(实际上只要在调用函数之前注册就行)
3、JNINativeMethod 中的数据类型可通过以下代码来获取,编译程序.class文件,在生成.h头文件

/* javac JNIDemo.java *//* javah -jni JNIDemo */

#include <stdio.h>#include <jni.h>#include <stdlib.h>/* java - c */#if 0typedef struct{char *name;   //name in javachar *signature;//paramschar *fnPtr;//name in C}JNINativeMethod;#endifvoid c_hello(JNIEnv *env, jobject cls){printf("hello\n");}jint c_add(JNIEnv *env, jobject cls, jint a, jint b){return a + b;}jstring c_str(JNIEnv *env, jobject cls, jstring str){const jbyte *cstr;cstr = (*env)->GetStringUTFChars(env, str, NULL);if (cstr == NULL) {return NULL; /* OutOfMemoryError already thrown */}printf("Get string from java :%s\n", cstr);(*env)->ReleaseStringUTFChars(env, str, cstr);return (*env)->NewStringUTF(env, "return from c");}jintArray c_array(JNIEnv *env, jobject cls, jintArray arr){jint *carr;jint *oarr;jintArray rarr;jint i, n = 0;carr = (*env)->GetIntArrayElements(env, arr, NULL);if (carr == NULL) {return 0; /* exception occurred */}n = (*env)->GetArrayLength(env, arr);oarr = malloc(sizeof(jint) * n);if (oarr == NULL){(*env)->ReleaseIntArrayElements(env, arr, carr, 0);return 0;}for (i = 0; i < n; i++){oarr[i] = carr[n-1-i];}(*env)->ReleaseIntArrayElements(env, arr, carr, 0);/* create jintArray */rarr = (*env)->NewIntArray(env, n);if (rarr == NULL){return 0;}(*env)->SetIntArrayRegion(env, rarr, 0, n, oarr);free(oarr);return rarr;}static const JNINativeMethod methods[] = {{"hello", "()V", (void *)c_hello},{"add", "(II)I", (void *)c_add},{"str", "(Ljava/lang/String;)Ljava/lang/String;", (void *)c_str},{"array","([I)[I", (void *)c_array},};JNI_OnLoad(JavaVM *jvm, void *reserved){JNIEnv *env;jclass cls;if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)){return JNI_ERR;}//function of class in javacls = (*env)->FindClass(env, "JNIDemo");if (cls == NULL){return JNI_ERR;}(*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0]));return JNI_VERSION_1_4;}


MediaScanner 分析:
MediaScanner.java (frameworks\base\media\java\android\media)
public class MediaScanner{    static {        System.loadLibrary("media_jni");  //libmedia_jni.so        native_init();    }    ...    //声明本地方法    private native void processDirectory(String path, MediaScannerClient client);    private native void processFile(String path, String mimeType, MediaScannerClient client);    public 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();    ...}
搜索一个 native 方法,processDirectory 来查找 JNI 层的代码位置:
android_media_MediaScanner.cpp (frameworks\base\media\jni)
static JNINativeMethod gMethods[] = {    {        "processDirectory",        "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",        (void *)android_media_MediaScanner_processDirectory    },    {        "processFile",        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",        (void *)android_media_MediaScanner_processFile    },    {        "setLocale",        "(Ljava/lang/String;)V",        (void *)android_media_MediaScanner_setLocale    },    {        "extractAlbumArt",        "(Ljava/io/FileDescriptor;)[B",        (void *)android_media_MediaScanner_extractAlbumArt    },    {        "native_init",        "()V",        (void *)android_media_MediaScanner_native_init    },    {        "native_setup",        "()V",        (void *)android_media_MediaScanner_native_setup    },    {        "native_finalize",        "()V",        (void *)android_media_MediaScanner_native_finalize    },};
这个文件中并没有 JNI_OnLoad 函数,但是实现了一个注册函数:
int register_android_media_MediaScanner(JNIEnv *env){    return AndroidRuntime::registerNativeMethods(env,                kClassMediaScanner, gMethods, NELEM(gMethods));}
android_media_MediaPlayer.cpp (frameworks\base\media\jni)
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_ImageReader(env) < 0) {        ALOGE("ERROR: ImageReader native registration failed");        goto bail;    }    if (register_android_media_MediaPlayer(env) < 0) {        ALOGE("ERROR: MediaPlayer native registration failed\n");        goto bail;    }    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;    }    ....}
在另一个文件中的 JNI_OnLoad 函数中,统一注册了一些 JNINativeMethods .
在 JNI 文件中可以获得 Java 中的成员变量、成员方法
static voidandroid_media_MediaScanner_native_init(JNIEnv *env){    ALOGV("native_init");    jclass clazz = env->FindClass(kClassMediaScanner);    if (clazz == NULL) {        return;    }    // java: private long mNativeContext;    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");//获得成员变量    if (fields.context == NULL) {        return;    }}static voidandroid_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){    ALOGV("native_setup");    MediaScanner *mp = new StagefrightMediaScanner;    if (mp == NULL) {        jniThrowException(env, kRunTimeException, "Out of memory");        return;    }    env->SetLongField(thiz, fields.context, (jlong)mp);//设置java中成员变量的值}

成员方法:
static voidandroid_media_MediaScanner_processDirectory(        JNIEnv *env, jobject thiz, jstring path, jobject client){    ALOGV("processDirectory");    MediaScanner *mp = getNativeScanner_l(env, thiz);        //unicode -> utf-8    const char *pathStr = env->GetStringUTFChars(path, NULL);    MyMediaScannerClient myClient(env, client);    ...    env->ReleaseStringUTFChars(path, pathStr);}
class MyMediaScannerClient : public MediaScannerClient{public:    MyMediaScannerClient(JNIEnv *env, jobject client)        :   mEnv(env),            mClient(env->NewGlobalRef(client)),            mScanFileMethodID(0),            mHandleStringTagMethodID(0),            mSetMimeTypeMethodID(0)    {        ALOGV("MyMediaScannerClient constructor");        jclass mediaScannerClientInterface =                env->FindClass(kClassMediaScannerClient);        if (mediaScannerClientInterface == NULL) {            ALOGE("Class %s not found", kClassMediaScannerClient);        } else {            //获得 java 中的成员方法            mScanFileMethodID = env->GetMethodID(                                    mediaScannerClientInterface,                                    "scanFile",                                    "(Ljava/lang/String;JJZZ)V");            mHandleStringTagMethodID = env->GetMethodID(                                    mediaScannerClientInterface,                                    "handleStringTag",                                    "(Ljava/lang/String;Ljava/lang/String;)V");            mSetMimeTypeMethodID = env->GetMethodID(                                    mediaScannerClientInterface,                                    "setMimeType",                                    "(Ljava/lang/String;)V");        }    }    ....    virtual status_t scanFile(const char* path, long long lastModified,            long long fileSize, bool isDirectory, bool noMedia)    {        mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,                fileSize, isDirectory, noMedia);        mEnv->DeleteLocalRef(pathStr);        return checkAndClearExceptionFromCallback(mEnv, "scanFile");    }private:    JNIEnv *mEnv;    jobject mClient;    jmethodID mScanFileMethodID;    jmethodID mHandleStringTagMethodID;    jmethodID mSetMimeTypeMethodID;};

mClient -> android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path,jobject client)
private native void processDirectory(String path, MediaScannerClient client);

public interface MediaScannerClient{        public void scanFile(String path, long lastModified, long fileSize,            boolean isDirectory, boolean noMedia);    /**     * Called by native code to return metadata extracted from media files.     */    public void handleStringTag(String name, String value);    /**     * Called by native code to return mime type extracted from DRM content.     */    public void setMimeType(String mimeType);}