前言
JNI不僅僅在NDK開發中應用,它更是Android系統中Java與Native交互的橋梁,不理解JNI的話,你就只能停留在Java Framework層。這一個系列我們來一起深入學習JNI。
1.JNI概述
Android系統按語言來划分的話由兩個世界組成,分別是Java世界和Native世界。那為什么要這么划分呢?Android系統由Java寫不好嗎?除了性能的之外,最主要的原因就是在Java誕生之前,就有很多程序和庫都是由Native語言寫的,因此,重復利用這些Native語言編寫的庫是十分必要的,況且Native語言編寫的庫具有更好的性能。
這樣就產生了一個問題,Java世界的代碼要怎么使用Native世界的代碼呢,這就需要一個橋梁來將它們連接在一起,而JNI就是這個橋梁。
通過JNI,Java世界的代碼就可以訪問Native世界的代碼,同樣的,Native世界的代碼也可以訪問Java世界的代碼。
為了講解JNI我們需要分析系統的源碼,在即將出版的《Android進階之光》的最后一章中我拿MediaPlayer框架做了舉例,這里換MediaRecorder框架來舉例,它和MediaPlayer框架的調用過程十分類似。
2.MediaRecorder框架概述
MediaRecorder我們應該都不陌生,它用於錄音和錄像。這里不會主要介紹MediaRecorder框架,而是MediaRecorder框架中的JNI。
Java世界對應的是MediaRecorder.java,也就是我們應用開發中直接調用的類。JNI層對用的是libmedia_jni.so,它是一個JNI的動態庫。Native層對應的是libmedia.so,這個動態庫完成了實際的調用的功能。
3.Java層的MediaRecorder
我們先來查看MediaRecorder.java的源碼,截取部分和JNI有關的部分如下所示。
frameworks/base/media/java/android/media/MediaRecorder.java
public class MediaRecorder{ static { System.loadLibrary("media_jni");//1 native_init();//2 } ... private static native final void native_init();//3 ... public native void start() throws IllegalStateException; ... }
在靜態代碼塊中首先調用了注釋1處的代碼,用來加載名為“media_jni“的動態庫,也就是libmedia_jni.so。接着調用注釋2處的native_init方法,注釋3處的native_init方法用native來修飾,說明它是一個native方法,表示由JNI來實現。MediaRecorder的start方法同樣也是一個native方法。
對於Java層來說只需要加載對應的JNI庫,接着聲明native方法就可以了,剩下的工作由JNI層來完成。
4.JNI層的MediaRecorder
MediaRecorder的JNI層由android_media_recorder.cpp實現,native方法native_init和start的JNI層實現如下所示。
frameworks/base/media/jni/android_media_MediaRecorder.cpp
static void android_media_MediaRecorder_native_init(JNIEnv *env) { jclass clazz; clazz = env->FindClass("android/media/MediaRecorder"); if (clazz == NULL) { return; } ... fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V"); if (fields.post_event == NULL) { return; } } static void android_media_MediaRecorder_start(JNIEnv *env, jobject thiz) { ALOGV("start"); sp<MediaRecorder> mr = getMediaRecorder(env, thiz); process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed."); }
android_media_MediaRecorder_native_init方法是native_init方法在JNI層的實現,android_media_MediaRecorder_start方法則是start方法在JNI層的實現。那么,native_init方法是如何找到對應的android_media_MediaRecorder_native_init方法的呢?
這就需要了解JNI方法注冊的知識。
5.JNI方法注冊
JNI方法注冊分為靜態注冊和動態注冊,其中靜態注冊多用於NDK開發,而動態注冊多用於Framework開發。
靜態注冊
native_init方法被聲明為注釋1處的方法,格式為Java_包名_類名_方法名
,注釋1處的方法名多了一個“l”,這是因為native_init方法有一個“_”,它會在轉換為JNI方法時變成“_l”。
其中JNIEnv * 是一個指向全部JNI方法的指針,該指針只在創建它的線程有效,不能跨線程傳遞。
jclass是JNI的數據類型,對應Java的java.lang.Class實例。jobject同樣也是JNI的數據類型,對應於Java的Object。關於JNIEnv * 以及JNI的數據類型會在本系列的后續文章中進行介紹。
當我們在Java中調用native_init方法時,就會從JNI中尋找Java_com_example_MediaRecorder_native_1init方法,如果沒有就會報錯,如果找到就會為native_init和Java_com_example_MediaRecorder_native_1init建立關聯,其實是保存JNI的方法指針,這樣再次調用native_init方法時就會直接使用這個方法指針就可以了。
靜態注冊就是根據方法名,將Java方法和JNI方法建立關聯,但是它有一些缺點:
- JNI層的方法名稱過長。
- 聲明Native方法的類需要用javah生成頭文件。
- 初次調用JIN方法時需要建立關聯,影響效率。
我們知道,靜態注冊就是Java的Native方法通過方法指針來與JNI進行關聯的,如果Native方法知道它在JNI中對應的方法指針,就可以避免上述的缺點,這就是動態注冊。
查看上一博文http://www.cnblogs.com/CZM-/p/7943572.html
動態注冊
JNI中有一種結構用來記錄Java的Native方法和JNI方法的關聯關系,它就是JNINativeMethod,它在jni.h中被定義:
typedef struct { const char* name;//Java方法的名字 const char* signature;//Java方法的簽名信息 void* fnPtr;//JNI中對應的方法指針 } JNINativeMethod;
HardControl.c這是一個jni點燈的demo,函數名字不需要跟java類有關系省去了編譯頭文件的步驟
#if 0 typedef struct { char *name; /* Java里調用的函數名 */ char *signature; /* JNI字段描述符, 用來表示Java里調用的函數的參數和返回值類型 */ void *fnPtr; /* C語言實現的本地函數 */ } JNINativeMethod; #endif static jint fd; #define DEVICE_NAME "/dev/leds" jint ledOpen(JNIEnv *env, jclass cls) { fd = open(DEVICE_NAME, O_RDWR); __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", DEVICE_NAME); __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen fd: %d", fd); if(fd >= 0) return 0; else return -1; return 0; } void ledClose(JNIEnv *env, jclass cls) { __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledClose......"); close(fd); } jint ledCtrl(JNIEnv *env, jclass cls,jint which, jint status) { int ret = ioctl(fd, status, which); __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl: %d, %d ret: %d",which, status, ret); return ret; } static const JNINativeMethod methods[] = { {"ledOpen", "()I", (void *)ledOpen}, {"ledClose", "()V", (void *)ledClose}, {"ledCtrl", "(II)I", (void *)ledCtrl}, }; /* System.loadLibrary */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { JNIEnv *env; jclass cls; if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) { return JNI_ERR; /* JNI version not supported */ } cls = (*env)->FindClass(env, "com/otherway/myapplication/HardControl"); if (cls == NULL) { return JNI_ERR; } /* 2. map hello java<-->c c_hello */ if((*env)->RegisterNatives(env, cls, methods, sizeof(methods) / sizeof(methods[0])) < 0) return JNI_ERR; return JNI_VERSION_1_4; }
HardControl.java
package com.otherway.myapplication; public class HardControl { public static native int ledCtrl(int which, int status); public static native int ledOpen(); public static native void ledClose(); static { try { System.loadLibrary("hardcontrol"); } catch (Exception e) { e.printStackTrace(); } } }
上面定義了一個JNINativeMethod類型的gMethods數組,里面存儲的就是HardControl的Native方法與JNI層方法的對應關系,其中注釋1處”ledClose”是Java層的Native方法,它對應的JNI層的方法為ledClose。”()V”是ledClose方法的簽名信息,關於Java方法的簽名信息后續的文章會介紹。
在不同的類空間使用直接修改cls = (*env)->FindClass(env, "com/otherway/myapplication/HardControl");即可