Android JNI原理分析
原文鏈接: http://gityuan.com/2016/05/28/android-jni/
引言:分析Android源碼6.0的過(guò)程,一定離不開(kāi)Java與C/C++代碼直接的來(lái)回跳轉(zhuǎn),那么就很有必要掌握J(rèn)NI,這是鏈接Java層和Native層的橋梁,本文涉及相關(guān)源碼:
frameworks/base/core/jni/AndroidRuntime.cpp
libcore/luni/src/main/java/java/lang/System.java
libcore/luni/src/main/java/java/lang/Runtime.java
libnativehelper/JNIHelp.cpp
libnativehelper/include/nativehelper/jni.h
frameworks/base/core/java/android/os/MessageQueue.java
frameworks/base/core/jni/android_os_MessageQueue.cpp
frameworks/base/core/java/android/os/Binder.java
frameworks/base/core/jni/android_util_Binder.cpp
frameworks/base/media/java/android/media/MediaPlayer.java
frameworks/base/media/jni/android_media_MediaPlayer.cpp
一、JNI概述
JNI(Java Native Interface,Java本地接口),用于打通Java層與Native(C/C++)層。這不是Android系統(tǒng)所獨(dú)有的,而是Java所有。眾所周知,Java語(yǔ)言是跨平臺(tái)的語(yǔ)言,而這跨平臺(tái)的背后都是依靠Java虛擬機(jī),虛擬機(jī)采用C/C++編寫(xiě),適配各個(gè)系統(tǒng),通過(guò)JNI為上層Java提供各種服務(wù),保證跨平臺(tái)性。
相信不少經(jīng)常使用Java的程序員,享受著其跨平臺(tái)性,可能全然不知JNI的存在。在Android平臺(tái),讓JNI大放異彩,為更多的程序員所熟知,往往為了提供效率或者其他功能需求,就需要NDK開(kāi)發(fā)。上一篇文章Linux系統(tǒng)調(diào)用(syscall)原理,介紹了打通android上層與底層kernel的樞紐syscall,那么本文的目的則是介紹打通android上層中Java層與Native的紐帶JNI。
二、JNI查找方式
Android系統(tǒng)在啟動(dòng)啟動(dòng)過(guò)程中,先啟動(dòng)Kernel創(chuàng)建init進(jìn)程,緊接著由init進(jìn)程fork第一個(gè)橫穿Java和C/C++的進(jìn)程,即Zygote進(jìn)程。Zygote啟動(dòng)過(guò)程中會(huì)AndroidRuntime.cpp中的startVm創(chuàng)建虛擬機(jī),VM創(chuàng)建完成后,緊接著調(diào)用startReg完成虛擬機(jī)中的JNI方法注冊(cè)。
2.1 startReg
[–>AndroidRuntime.cpp]
int?AndroidRuntime::startReg(JNIEnv*?env)
{
????//設(shè)置線程創(chuàng)建方法為javaCreateThreadEtc
????androidSetCreateThreadFunc((android_create_thread_fn)?javaCreateThreadEtc);
????env->PushLocalFrame(200);
????//進(jìn)程JNI方法的注冊(cè)
????if?(register_jni_procs(gRegJNI,?NELEM(gRegJNI),?env)?0)?{
????????env->PopLocalFrame(NULL);
????????return?-1;
????}
????env->PopLocalFrame(NULL);
????return?0;
}
register_jni_procs(gRegJNI, NELEM(gRegJNI), env)這行代碼的作用就是就是循環(huán)調(diào)用gRegJNI數(shù)組成員所對(duì)應(yīng)的方法。
static?int?register_jni_procs(const?RegJNIRec?array[],?size_t?count,?JNIEnv*?env)
{
????for?(size_t?i?=?0;?i?????????if?(array[i].mProc(env)?0)?{
????????????return?-1;
????????}
????}
????return?0;
}
gRegJNI數(shù)組,有100多個(gè)成員變量,定義在AndroidRuntime.cpp:
static?const?RegJNIRec?gRegJNI[]?=?{
????REG_JNI(register_android_os_MessageQueue),
????REG_JNI(register_android_os_Binder),
????...
};
該數(shù)組的每個(gè)成員都代表一個(gè)類文件的jni映射,其中REG_JNI是一個(gè)宏定義,在Zygote中介紹過(guò),該宏的作用就是調(diào)用相應(yīng)的方法。
2.2 如何查找native方法
當(dāng)大家在看framework層代碼時(shí),經(jīng)常會(huì)看到native方法,這是往往需要查看所對(duì)應(yīng)的C++方法在哪個(gè)文件,對(duì)應(yīng)哪個(gè)方法?下面從一個(gè)實(shí)例出發(fā)帶大家如何查看java層方法所對(duì)應(yīng)的native方法位置。
2.2.1 實(shí)例(一)
當(dāng)分析Android消息機(jī)制源碼,遇到MessageQueue.java中有多個(gè)native方法,比如:
package?android.os;
public?final?class?MessageQueue?{
????private?native?void?nativePollOnce(long?ptr,?int?timeoutMillis);
}
步驟1: MessageQueue.java的全限定名為android.os.MessageQueue.java,完整的方法名為android.os.MessageQueue.nativePollOnce(),與之相對(duì)應(yīng)的native層方法名是將點(diǎn)號(hào)替換為下劃線,即android_os_MessageQueue_nativePollOnce()。
步驟2: 有了native方法,那么接下來(lái)需要知道該native方法所在那個(gè)文件。前面已經(jīng)介紹過(guò)Android系統(tǒng)啟動(dòng)時(shí)就已經(jīng)注冊(cè)了大量的JNI方法,見(jiàn)AndroidRuntime.cpp的gRegJNI數(shù)組。這些注冊(cè)方法命令方式:
那么MessageQueue.java所定義的jni注冊(cè)方法名應(yīng)該是register_android_os_MessageQueue,的確存在于gRegJNI數(shù)組,說(shuō)明這次JNI注冊(cè)過(guò)程是在開(kāi)機(jī)過(guò)程完成。該方法在AndroidRuntime.cpp申明為extern方法:
extern?int?register_android_os_MessageQueue(JNIEnv*?env);
這些extern方法絕大多數(shù)位于/framework/base/core/jni/目錄,大多數(shù)情況下native文件命名方式:
[包名]_[類名].cpp
[包名]_[類名].h
Tips: /android/os路徑下的MessageQueue.java ==> android_os_MessageQueue.cpp
打開(kāi)android_os_MessageQueue.cpp文件,搜索android_os_MessageQueue_nativePollOnce方法,這便找到了目標(biāo)方法:
static?void?android_os_MessageQueue_nativePollOnce(JNIEnv*?env,?jobject?obj,
????????jlong?ptr,?jint?timeoutMillis)?{
????NativeMessageQueue*?nativeMessageQueue?=?reinterpret_cast(ptr);
????nativeMessageQueue->pollOnce(env,?obj,?timeoutMillis);
}
到這里完成了一次從Java層方法搜索到所對(duì)應(yīng)的C++方法的過(guò)程。
2.2.2 實(shí)例(二)
對(duì)于native文件命名方式,有時(shí)并非[包名]_[類名].cpp,比如/android/os路徑下的Binder.java 所對(duì)應(yīng)的native文件:android_util_Binder.cpp
package?android.os;
public?class?Binder?implements?IBinder?{
????public?static?final?native?int?getCallingPid();
}
根據(jù)實(shí)例(一)方式,找到getCallingPid ==> android_os_Binder_getCallingPid(),并且在AndroidRuntime.cpp中的gRegJNI數(shù)組中找到register_android_os_Binder。
按實(shí)例(一)方式則native文名應(yīng)該為android_os_Binder.cpp,可是在/framework/base/core/jni/目錄下找不到該文件,這是例外的情況。其實(shí)真正的文件名為android_util_Binder.cpp,這就是例外,這一點(diǎn)有些費(fèi)勁,不明白為何google要如此打破規(guī)律的命名。
static?jint?android_os_Binder_getCallingPid(JNIEnv*?env,?jobject?clazz)
{
????return?IPCThreadState::self()->getCallingPid();
}
有人可能好奇,既然如何遇到打破常規(guī)的文件命令,怎么辦?這個(gè)并不難,首先,可以嘗試在/framework/base/core/jni/中搜索,對(duì)于binder.java,可以直接搜索binder關(guān)鍵字,其他也類似。如果這里也找不到,可以通過(guò)grep全局搜索android_os_Binder_getCallingPid這個(gè)方法在哪個(gè)文件。
jni存在的常見(jiàn)目錄:
/framework/base/core/jni//framework/base/services/core/jni//framework/base/media/jni/
2.2.3 實(shí)例(三)
前面兩種都是在Android系統(tǒng)啟動(dòng)之初,便已經(jīng)注冊(cè)過(guò)JNI所對(duì)應(yīng)的方法。那么如果程序自己定義的jni方法,該如何查看jni方法所在位置呢?下面以MediaPlayer.java為例,其包名為android.media:
public?class?MediaPlayer{
????static?{
????????System.loadLibrary("media_jni");
????????native_init();
????}
????private?static?native?final?void?native_init();
????...
}
通過(guò)static靜態(tài)代碼塊中System.loadLibrary方法來(lái)加載動(dòng)態(tài)庫(kù),庫(kù)名為media_jni, Android平臺(tái)則會(huì)自動(dòng)擴(kuò)展成所對(duì)應(yīng)的libmedia_jni.so庫(kù)。接著通過(guò)關(guān)鍵字native加在native_init方法之前,便可以在java層直接使用native層方法。
接下來(lái)便要查看libmedia_jni.so庫(kù)定義所在文件,一般都是通過(guò)Android.mk文件定義LOCAL_MODULE:= libmedia_jni,可以采用grep或者mgrep來(lái)搜索包含libmedia_jni字段的Android.mk所在路徑。
搜索可知,libmedia_jni.so位于/frameworks/base/media/jni/Android.mk。用前面實(shí)例(一)中的知識(shí)來(lái)查看相應(yīng)的文件和方法名分別為:
android_media_MediaPlayer.cpp
android_media_MediaPlayer_native_init()
再然后,你會(huì)發(fā)現(xiàn)果然在該Android.mk所在目錄/frameworks/base/media/jni/中找到android_media_MediaPlayer.cpp文件,并在文件中存在相應(yīng)的方法:
??static?void
android_media_MediaPlayer_native_init(JNIEnv?*env)
{
????jclass?clazz;
????clazz?=?env->FindClass("android/media/MediaPlayer");
????fields.context?=?env->GetFieldID(clazz,?"mNativeContext",?"J");
????...
}
Tips:MediaPlayer.java中的native_init方法所對(duì)應(yīng)的native方法位于/frameworks/base/media/jni/目錄下的android_media_MediaPlayer.cpp文件中的android_media_MediaPlayer_native_init方法。
2.3 小結(jié)
JNI作為連接Java世界和C/C++世界的橋梁,很有必要掌握??赐瓯疚?,至少能掌握在分析Android源碼過(guò)程中如何查找native方法。首先要明白native方法名和文件名的命名規(guī)律,其次要懂得該如何去搜索代碼。
JNI注冊(cè)的兩種時(shí)機(jī):
Android系統(tǒng)啟動(dòng)過(guò)程中Zygote注冊(cè),可通過(guò)查詢AndroidRuntime.cpp中的gRegJNI,看看是否存在對(duì)應(yīng)的register方法;
調(diào)用System.loadLibrary()方式注冊(cè)。
三、 JNI原理分析
再進(jìn)一步來(lái)分析,Java層與Native層方法是如何注冊(cè)并映射的,以MediaPlayer為例。文件MediaPlayer.java中調(diào)用System.loadLibrary(“media_jni”),把libmedia_jni.so動(dòng)態(tài)庫(kù)加載到內(nèi)存。以loadLibrary為起點(diǎn)展開(kāi)JNI注冊(cè)流程的過(guò)程分析。
3.1 loadLibrary
[System.java]
public?static?void?loadLibrary(String?libName)?{
????//調(diào)用Runtime方法
????Runtime.getRuntime().loadLibrary(libName,?VMStack.getCallingClassLoader());
}
[Runtime.java]
void?loadLibrary(String?libraryName,?ClassLoader?loader)?{
????if?(loader?!=?null)?{
????????String?filename?=?loader.findLibrary(libraryName);
????????//加載庫(kù)【見(jiàn)小節(jié)3.2】
????????String?error?=?doLoad(filename,?loader);
????????if?(error?!=?null)?{
????????????throw?new?UnsatisfiedLinkError(error);
????????}
????????return;
????}
????//loader為空,則會(huì)進(jìn)入該分支
????String?filename?=?System.mapLibraryName(libraryName);
????List<String>?candidates?=?new?ArrayList<String>();
????for?(String?directory?:?mLibPaths)?{
????????String?candidate?=?directory?+?filename;
????????candidates.add(candidate);
????????if?(IoUtils.canOpenReadOnly(candidate))?{
?????????????//加載庫(kù)【見(jiàn)小節(jié)3.2】
????????????String?error?=?doLoad(candidate,?loader);
????????????if?(error?==?null)?{
????????????????return;?//加載成功
????????????}
????????}
????}
????...
}
真正加載的工作是由doLoad(),更多詳情見(jiàn)loadLibrary動(dòng)態(tài)庫(kù)加載過(guò)程分析。
3.2 doLoad
該方法內(nèi)部增加同步鎖,保證并發(fā)時(shí)一致性。
private?String?doLoad(String?name,?ClassLoader?loader)?{
????...
????synchronized?(this)?{
????????return?nativeLoad(name,?loader,?ldLibraryPath);
????}
}
nativeLoad()這是一個(gè)native方法,再進(jìn)入ART虛擬機(jī)的java_lang_Runtime.cc,最終的核心功能工作:
調(diào)用
dlopen函數(shù),打開(kāi)一個(gè)so文件并創(chuàng)建一個(gè)handle;調(diào)用
dlsym()函數(shù),查看相應(yīng)so文件的JNI_OnLoad()函數(shù)指針,并執(zhí)行相應(yīng)函數(shù)。
總之,System.loadLibrary()的作用就是調(diào)用相應(yīng)庫(kù)中的JNI_OnLoad()方法。接下來(lái)說(shuō)說(shuō)JNI_OnLoad()過(guò)程。
3.2 JNI_OnLoad
[-> android_media_MediaPlayer.cpp]
jint?JNI_OnLoad(JavaVM*?vm,?void*?reserved)
{
????JNIEnv*?env?=?NULL;
????//【見(jiàn)3.3】?注冊(cè)JNI方法
????if?(register_android_media_MediaPlayer(env)?0)?{
????????goto?bail;
????}
????...
}
3.3 register_android_media_MediaPlayer
[-> android_media_MediaPlayer.cpp]
static?int?register_android_media_MediaPlayer(JNIEnv?*env)
{
????//【見(jiàn)3.4】
????return?AndroidRuntime::registerNativeMethods(env,
????????????????"android/media/MediaPlayer",?gMethods,?NELEM(gMethods));
}
其中gMethods,記錄java層和C/C++層方法的一一映射關(guān)系。
static?JNINativeMethod?gMethods[]?=?{
????{"prepare",??????"()V",??(void?*)android_media_MediaPlayer_prepare},
????{"_start",???????"()V",??(void?*)android_media_MediaPlayer_start},
????{"_stop",????????"()V",??(void?*)android_media_MediaPlayer_stop},
????{"seekTo",???????"(I)V",?(void?*)android_media_MediaPlayer_seekTo},
????{"_release",?????"()V",??(void?*)android_media_MediaPlayer_release},
????{"native_init",??"()V",??(void?*)android_media_MediaPlayer_native_init},
????...
};
這里涉及到結(jié)構(gòu)體JNINativeMethod,其定義在jni.h文件:
typedef?struct?{
????const?char*?name;??//Java層native函數(shù)名
????const?char*?signature;?//Java函數(shù)簽名,記錄參數(shù)類型和個(gè)數(shù),以及返回值類型
????void*???????fnPtr;?//Native層對(duì)應(yīng)的函數(shù)指針
}?JNINativeMethod;
關(guān)于函數(shù)簽名signature在下一小節(jié)展開(kāi)說(shuō)明。
3.4 registerNativeMethods
[-> AndroidRuntime.cpp]
int?AndroidRuntime::registerNativeMethods(JNIEnv*?env,
????const?char*?className,?const?JNINativeMethod*?gMethods,?int?numMethods)
{
????//【見(jiàn)3.5】
????return?jniRegisterNativeMethods(env,?className,?gMethods,?numMethods);
}
jniRegisterNativeMethods該方法是由Android JNI幫助類JNIHelp.cpp來(lái)完成。
3.5 jniRegisterNativeMethods
[-> JNIHelp.cpp]
extern?"C"?int?jniRegisterNativeMethods(C_JNIEnv*?env,?const?char*?className,
????const?JNINativeMethod*?gMethods,?int?numMethods)
{
????JNIEnv*?e?=?reinterpret_cast(env);
????scoped_local_ref?c(env,?findClass(env,?className));
????if?(c.get()?==?NULL)?{
????????e->FatalError("");//無(wú)法查找native注冊(cè)方法
????}
????//【見(jiàn)3.6】?調(diào)用JNIEnv結(jié)構(gòu)體的成員變量
????if?((*env)->RegisterNatives(e,?c.get(),?gMethods,?numMethods)?0)?{
????????e->FatalError("");//native方法注冊(cè)失敗
????}
????return?0;
}
3.6 RegisterNatives
[-> jni.h]
struct?_JNIEnv?{
????const?struct?JNINativeInterface*?functions;
????jint?RegisterNatives(jclass?clazz,?const?JNINativeMethod*?methods,
????????????jint?nMethods)
????{?return?functions->RegisterNatives(this,?clazz,?methods,?nMethods);?}
????...
}
functions是指向JNINativeInterface結(jié)構(gòu)體指針,也就是將調(diào)用下面方法:
struct?JNINativeInterface?{
????jint?(*RegisterNatives)(JNIEnv*,?jclass,?const?JNINativeMethod*,jint);
????...
}
再往下深入就到了虛擬機(jī)內(nèi)部吧,這里就不再往下深入了??傊@個(gè)過(guò)程完成了gMethods數(shù)組中的方法的映射關(guān)系,比如java層的native_init()方法,映射到native層的android_media_MediaPlayer_native_init()方法。
虛擬機(jī)相關(guān)的變量中有兩個(gè)非常重要的量JavaVM和JNIEnv:
JavaVM:是指進(jìn)程虛擬機(jī)環(huán)境,每個(gè)進(jìn)程有且只有一個(gè)JavaVM實(shí)例JNIEnv:是指線程上下文環(huán)境,每個(gè)線程有且只有一個(gè)JNIEnv實(shí)例,
四、JNI資源
JNINativeMethod結(jié)構(gòu)體中有一個(gè)字段為signature(簽名),再介紹signature格式之前需要掌握各種數(shù)據(jù)類型在Java層、Native層以及簽名所采用的簽名格式。
4.1 數(shù)據(jù)簽名
4.1.1 基本數(shù)據(jù)類型
| Signature格式 | Java | Native |
|---|---|---|
| B | byte | jbyte |
| C | char | jchar |
| D | double | jdouble |
| F | float | jfloat |
| I | int | jint |
| S | short | jshort |
| J | long | jlong |
| Z | boolean | jboolean |
| V | void | void |
4.1.2 數(shù)組數(shù)據(jù)類型
數(shù)組簡(jiǎn)稱則是在前面添加[:
| Signature格式 | Java | Native |
|---|---|---|
| [B | byte[] | jbyteArray |
| [C | char[] | jcharArray |
| [D | double[] | jdoubleArray |
| [F | float[] | jfloatArray |
| [I | int[] | jintArray |
| [S | short[] | jshortArray |
| [J | long[] | jlongArray |
| [Z | boolean[] | jbooleanArray |
4.1.3 復(fù)雜數(shù)據(jù)類型
對(duì)象類型簡(jiǎn)稱:L+classname +;
| Signature格式 | Java | Native |
|---|---|---|
| Ljava/lang/String; | String | jstring |
| L+classname +; | 所有對(duì)象 | jobject |
| [L+classname +; | Object[] | jobjectArray |
| Ljava.lang.Class; | Class | jclass |
| Ljava.lang.Throwable; | Throwable | jthrowable |
4.1.4 Signature
有了前面的鋪墊,那么再來(lái)通過(guò)實(shí)例說(shuō)說(shuō)函數(shù)簽名:(輸入?yún)?shù)...)返回值參數(shù),這里用到的便是前面介紹的Signature格式。
| Java函數(shù) | 對(duì)應(yīng)的簽名 |
|---|---|
| void foo() | ()V |
| float foo(int i) | (I)F |
| long foo(int[] i) | ([I)J |
| double foo(Class c) | (Ljava/lang/Class;)D |
| boolean foo(int[] i,String s) | ([ILjava/lang/String;)Z |
| String foo(int i) | (I)Ljava/lang/String; |
4.2 其他
(一)垃圾回收 對(duì)于Java開(kāi)發(fā)人員來(lái)說(shuō)無(wú)需關(guān)系垃圾回收,完全由虛擬機(jī)GC來(lái)負(fù)責(zé)垃圾回收,而對(duì)于JNI開(kāi)發(fā)人員,對(duì)于內(nèi)存釋放需要謹(jǐn)慎處理,需要的時(shí)候申請(qǐng),使用完記得釋放內(nèi)容,以免發(fā)生內(nèi)存泄露。在JNI提供了三種Reference類型,Local Reference(本地引用), Global Reference(全局引用), Weak Global Reference(全局弱引用)。其中Global Reference如果不主動(dòng)釋放,則一直不會(huì)釋放;對(duì)于其他兩個(gè)類型的引用都是釋放的可能性,那是不是意味著不需要手動(dòng)釋放呢?答案是否定的,不管是這三種類型的那種引用,都盡可能在某個(gè)內(nèi)存不再需要時(shí),立即釋放,這對(duì)系統(tǒng)更為安全可靠,以減少不可預(yù)知的性能與穩(wěn)定性問(wèn)題。
另外,ART虛擬機(jī)在GC算法有所優(yōu)化,為了減少內(nèi)存碎片化問(wèn)題,在GC之后有可能會(huì)移動(dòng)對(duì)象內(nèi)存的位置,對(duì)于Java層程序并沒(méi)有影響,但是對(duì)于JNI程序可要小心了,對(duì)于通過(guò)指針來(lái)直接訪問(wèn)內(nèi)存對(duì)象是,Dalvik能正確運(yùn)行的程序,ART下未必能正常運(yùn)行。
(二)異常處理 Java層出現(xiàn)異常,虛擬機(jī)會(huì)直接拋出異常,這是需要try..catch或者繼續(xù)往外throw。但是對(duì)于JNI出現(xiàn)異常時(shí),即執(zhí)行到JNIEnv中某個(gè)函數(shù)異常時(shí),并不會(huì)立即拋出異常來(lái)中斷程序的執(zhí)行,還可以繼續(xù)執(zhí)行內(nèi)存之類的清理工作,直到返回到Java層時(shí)才會(huì)拋出相應(yīng)的異常。
另外,Dalvik虛擬機(jī)有些情況下JNI函數(shù)出錯(cuò)可能返回NULL,但ART虛擬機(jī)在出錯(cuò)時(shí)更多的是拋出異常。這樣導(dǎo)致的問(wèn)題就可能是在Dalvik版本能正常運(yùn)行的程序,在ART虛擬機(jī)上由于沒(méi)有正確處理異常而崩潰。
總結(jié)
本文主要通過(guò)實(shí)例,在源碼視角分析JNI原理,講述JNI核心功能:
介紹如何查找JNI方法,明白如何從Java層跳轉(zhuǎn)到Native層;
分析JNI函數(shù)注冊(cè)流程,核心是通過(guò)JNIEnv的RegisterNatives()方法來(lái)完成注冊(cè);
列舉Java與native以及函數(shù)簽名方式。
jni存在的常見(jiàn)目錄:
/framework/base/core/jni//framework/base/services/core/jni//framework/base/media/jni/
微信公眾號(hào) Gityuan | 微博 weibo.com/gityuan | 博客 留言區(qū)交流
推薦閱讀:
NDK | 帶你梳理 JNI 函數(shù)注冊(cè)的方式和時(shí)機(jī)
Android NDK 開(kāi)發(fā):JNI 基礎(chǔ)篇
Android NDK 開(kāi)發(fā):Java 與 Native 相互調(diào)用
NDK 開(kāi)發(fā)中 Native 方法的靜態(tài)注冊(cè)與動(dòng)態(tài)注冊(cè)
Android NDK 開(kāi)發(fā)中快速定位 Crash 問(wèn)題
Android JNI 中發(fā)送 Http 網(wǎng)絡(luò)請(qǐng)求
