JNI不只僅在NDK開發中應用,它更是Android系統中Java與Native交互的橋樑,不理解JNI的話,你就只能停留在Java Framework層。這一個系列咱們來一塊兒深刻學習JNI。
javascript
Android系統按語言來劃分的話由兩個世界組成,分別是Java世界和Native世界。那爲何要這麼劃分呢?Android系統由Java寫很差嗎?除了性能的以外,最主要的緣由就是在Java誕生以前,就有不少程序和庫都是由Native語言寫的,所以,重複利用這些Native語言編寫的庫是十分必要的,何況Native語言編寫的庫具備更好的性能。
這樣就產生了一個問題,Java世界的代碼要怎麼使用Native世界的代碼呢,這就須要一個橋樑來將它們鏈接在一塊兒,而JNI就是這個橋樑。
java
MediaRecorder咱們應該都不陌生,它用於錄音和錄像。這裏不會主要介紹MediaRecorder框架,而是MediaRecorder框架中的JNI。
android
咱們先來查看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層來完成。微信
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方法註冊的知識。post
JNI方法註冊分爲靜態註冊和動態註冊,其中靜態註冊多用於NDK開發,而動態註冊多用於Framework開發。性能
在AS中新建一個Java Library名爲media,這裏仿照系統的MediaRecorder.java,寫一個簡單的MediaRecorder.java,以下所示。學習
package com.example;
public class MediaRecorder {
static {
System.loadLibrary("media_jni");
native_init();
}
private static native final void native_init();
public native void start() throws IllegalStateException;
}複製代碼
接着進入項目的media/src/main/java目錄中執行以下命令:spa
javac com.example.MediaRecorder.java
javah com.example.MediaRecorder複製代碼
第二個命令會在當前目錄中(media/src/main/java)生成com_example_MediaRecorder.h文件,以下所示。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_MediaRecorder */
#ifndef _Included_com_example_MediaRecorder
#define _Included_com_example_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/* * Class: com_example_MediaRecorder * Method: native_init * Signature: ()V */
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_native_1init (JNIEnv *, jclass);//1
/* * Class: com_example_MediaRecorder * Method: start * Signature: ()V */
JNIEXPORT void JNICALL Java_com_example_MediaRecorder_start (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif複製代碼
nativeinit方法被聲明爲註釋1處的方法,格式爲`Java包名類名方法名`,註釋1處的方法名多了一個「l」,這是由於nativeinit方法有一個「」,它會在轉換爲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方法創建關聯,可是它有一些缺點:
咱們知道,靜態註冊就是Java的Native方法經過方法指針來與JNI進行關聯的,若是Native方法知道它在JNI中對應的方法指針,就能夠避免上述的缺點,這就是動態註冊。
JNI中有一種結構用來記錄Java的Native方法和JNI方法的關聯關係,它就是JNINativeMethod,它在jni.h中被定義:
typedef struct {
const char* name;//Java方法的名字
const char* signature;//Java方法的簽名信息
void* fnPtr;//JNI中對應的方法指針
} JNINativeMethod;複製代碼
系統的MediaRecorder採用的就是動態註冊,咱們來查看它的JNI層是怎麼作的。
frameworks/base/media/jni/android_media_MediaRecorder.cpp
static const JNINativeMethod gMethods[] = {
...
{"start", "()V", (void *)android_media_MediaRecorder_start},//1
{"stop", "()V", (void *)android_media_MediaRecorder_stop},
{"pause", "()V", (void *)android_media_MediaRecorder_pause},
{"resume", "()V", (void *)android_media_MediaRecorder_resume},
{"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset},
{"release", "()V", (void *)android_media_MediaRecorder_release},
{"native_init", "()V", (void *)android_media_MediaRecorder_native_init},
...
};複製代碼
上面定義了一個JNINativeMethod類型的gMethods數組,裏面存儲的就是MediaRecorder的Native方法與JNI層方法的對應關係,其中註釋1處"start"是Java層的Native方法,它對應的JNI層的方法爲android_media_MediaRecorder_start。"()V"是start方法的簽名信息,關於Java方法的簽名信息後續的文章會介紹。
只定義JNINativeMethod 類型的數組是沒有用的,還須要註冊它,註冊的方法爲register_android_media_MediaRecorder:
frameworks/base/media/jni/android_media_MediaRecorder.cpp
//JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaRecorder(JNIEnv *env) {
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaRecorder", gMethods, NELEM(gMethods));
}複製代碼
register_android_media_MediaRecorder方法中return了AndroidRuntime的registerNativeMethods方法,以下所示。
frameworks/base/core/jni/AndroidRuntime.cpp
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}複製代碼
registerNativeMethods方法中又return了jniRegisterNativeMethods方法:
external/conscrypt/src/openjdk/native/JNIHelp.cpp
extern "C" int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
...
if (env->RegisterNatives(c.get(), gMethods, numMethods) < 0) {//1
char* msg;
(void)asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
env->FatalError(msg);
}
return 0;
}複製代碼
從註釋1處能夠看出,最終調用的JNIEnv的RegisterNatives方法,JNIEnv在JNI中十分重要,後續文章會介紹它。
register_android_media_MediaRecorder方法最終會調用JNIEnv的RegisterNatives方法,可是register_android_media_MediaRecorder方法是在哪被調用的呢?答案在register_android_media_MediaRecorder方法的註釋上:JNI_OnLoad in android_media_MediaPlayer.cpp。這個JNI_OnLoad方法會在System.loadLibrary方法後調用,由於多媒體框架中的不少框架都要進行JNINativeMethod數組註冊,所以,註冊方法就被統必定義在android_media_MediaPlayer.cpp中的JNI_OnLoad方法中,以下所示。
frameworks/base/media/jni/android_media_MediaPlayer.cpp
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_MediaPlayer(env) < 0) {
ALOGE("ERROR: MediaPlayer native registration failed\n");
goto *bail;
}
if (register_android_media_MediaRecorder(env) < 0) {//1
ALOGE("ERROR: MediaRecorder native registration failed\n");
goto *bail;
}
...
result = JNI_VERSION_1_4;
bail:
return result;
}複製代碼
在JNI_OnLoad方法中調用了整個多媒體框架的註冊JNINativeMethod數組的方法,註釋1處的調用了register_android_media_MediaRecorder方法,一樣的,MediaPlayer框架的註冊JNINativeMethod數組的方法register_android_media_MediaPlayer也被調用了。
關於動態註冊就講到這裏,更多深刻JNI的知識請見本系列後續的文章。
參考資料
《深刻理解Android卷I》
歡迎關注個人微信公衆號,第一時間得到博客更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,便可關注。