JNI專題1

Android系統(tǒng)源碼分析-JNI


因為在接下來的源碼分析中將涉及大量的Java和Native的互相調用怖糊。當然對于我們的代碼分析沒有什么影響帅容,但是,這樣一個黑盒子擺在面前伍伤,對于其實現(xiàn)原理還是充滿了好奇心并徘。本篇將從JNI最基本的概念到簡單的代碼實例和其實現(xiàn)原理逐步展開。序言

1.JNI

JNI(Java Native Interface,Java本地接口)是一種編程框架使得Java虛擬機中的Java程序可以調用本地應用/或庫,也可以被其他程序調用扰魂。 本地程序一般是用其它語言C麦乞,C++或匯編語言編寫的, 并且被編譯為基于本機硬件和操作系統(tǒng)的程序。在Android平臺劝评,為了更方便開發(fā)者的使用和增強其功能性姐直,Android提供了NDK來更方便開發(fā)者的開發(fā)。

2.為什么要有JNI蒋畜?

JNI允許程序員用其他編程語言來解決用純粹的Java代碼不好處理的情況, 例如, Java標準庫不支持的平臺相關功能或者程序庫声畏。也用于改造已存在的用其它語言寫的程序, 供Java程序調用。許多基于JNI的標準庫提供了很多功能給程序員使用, 例如文件I/O姻成、音頻相關的功能插龄。當然,也有各種高性能的程序科展,以及平臺相關的API實現(xiàn), 允許所有Java應用程序安全并且平臺獨立地使用這些功能均牢。Java層可以用來負責UI功能實現(xiàn),而C++負責進行計算操作辛润。

JNI框架允許Native方法調用Java對象膨处,就像Java程序訪問Native對象一樣方便见秤。Native方法可以創(chuàng)建Java對象砂竖,讀取這些對象, 并調用Java對象執(zhí)行某些方法。當然Native方法也可以讀取由Java程序自身創(chuàng)建的對象,并調用這些對象的方法鹃答。

3.Hello World

這里乎澄,我們先通過一個簡單的Hello World實例來對JNI的調用流程有一個直觀的印象,然后針對其中的實現(xiàn)原理和細節(jié)做分析测摔。

在Java文件中定義native函數(shù)

在此方法聲明中置济,使用 native 關鍵字的作用是告訴虛擬機解恰,函數(shù)位于共享庫中(即在原生端實現(xiàn))。

private?native?String?helloWorld();

利用Javah生成頭文件

對于native方法的命名規(guī)則浙于,函數(shù)名根據(jù)以下規(guī)則構建:

在名稱前面加上 Java_护盈。

描述與頂級源目錄相關的文件路徑。

使用下劃線代替正斜杠羞酗。

刪掉 .java 文件擴展名腐宋。

在最后一個下劃線后,附加函數(shù)名檀轨。

按照這些規(guī)則胸竞,此示例使用的函數(shù)名為Java_com_example_hellojni_HelloJni_stringFromJNI。 此名稱描述hellojni/src/com/example/hellojni/HelloJni.java中一個名為 stringFromJNI()的 Java 函數(shù)参萄。我們想通過更簡單的方式卫枝,讓寫native函數(shù)如同和寫java函數(shù)沒有這一步的轉化,那么可以通過javah來實現(xiàn)讹挎。

javah -d ../jni -jni?com.chenjensen.myapplication.MainActivity

d :頭文件輸出目錄

jni:生成jni文件

根據(jù)Javah生成的頭文件校赤,實現(xiàn)相應的native函數(shù)

JNIEXPORT jstring JNICALL?

Java_com_chenjensen_myapplication_MainActivity_helloWorld

(JNIEnv *, jobject)

;

頭文件中生成了我們的java文件中定義的native方法,也做好了類型轉化筒溃,我們只需要新建一個cpp文件來實現(xiàn)相應的方法即可痒谴。

cpp文件

JNIEXPORT jstring JNICALL

Java_com_chenjensen_myapplication_MainActivity_helloWorld

(JNIEnv *env, jobject)

{

char?*str =?"Hello world";

return?(*env).NewStringUTF(str);

}

build文件中編譯支持指定的平臺(arm,x86等)

ndk?{

?moduleName?"hello"??

abiFilters?"armeabi",?"armeabi-v7a",?"x86"?}

這里指定了生成so文件的name之后铡羡,編譯系統(tǒng)就會從JNI目錄下去尋找相應的c/cpp文件积蔚,來生成相應的so文件。

執(zhí)行

在Java代碼中烦周,native方法的執(zhí)行之前尽爆,要提前加載相應的動態(tài)庫,然后才可以執(zhí)行读慎,一般會在該類中通過靜態(tài)代碼塊的方式來加載漱贱。應用啟動時,調用此函數(shù)以加載 .so 文件夭委。

static?{

System.loadLibrary("hello");

}

這個時候幅狮,我們在Java代碼中調用相應的native代碼就會生效了。

那么在C/C++文件中如何調用Java呢株灸,這里的調用方式和Java中通過反射查找一個類的調用相似崇摄。核心函數(shù)為以下幾個。

FindClass(),?

NewObject(),?

GetStaticMethodID(),?

GetMethodID(),?

CallStaticObjectMethod(),

CallVoidMethod()

找到相應的類慌烧,相應的方法逐抑,調用相應的類和方法。這里不在給出具體的代碼示例屹蚊〔薨保可參考文章末尾給出的相應鏈接进每。

4.如何調用

通過上述6個步驟,我們便實現(xiàn)了Java調用native函數(shù)命斧,借助了相應的工具田晚,我們可以很快的實現(xiàn)其互相調用,但是国葬,工具也屏蔽掉了大量的實現(xiàn)細節(jié)肉瓦,讓這個過程變成黑盒,不了解其實現(xiàn)胃惜。這個過程中泞莉,

當JVM調用這些函數(shù),傳遞了一個JNIEnv指針船殉,一個jobject的指針鲫趁,任何在Java方法中聲明的Java參數(shù)。

一個JNI函數(shù)看起來類似這樣:

JNIEXPORT?void?JNICALL?Java_ClassName_MethodName

(JNIEnv *env, jobject obj)

{

}

Java和C++之間的調用利虫,Java的執(zhí)行需要在JVM上挨厚,因此在調用的時候,JVM必須知道要調用那一個本地函數(shù)糠惫,本地函數(shù)調用Java的時候疫剃,也必須要知道應用對象和具體的函數(shù)。

JNI中C++和Java的執(zhí)行是在同一個線程硼讽,但是其線程值是不相同的巢价。

JNIEnv是JNI的使用環(huán)境,JNIEnv對象是和線程綁定在一起的固阁,在進行調用的時候壤躲,會傳遞一個JavaVM的指針作為參數(shù),然后通過JavaVM的getEnv函數(shù)得到JNIEnv對象的指針备燃。在Java中每次創(chuàng)建一個線程碉克,都會生成新的JNIEnv對象。

在分析系統(tǒng)源碼的時候并齐,我們可以看到很多的java對于native的調用漏麦,通過對于源碼的分析,我們發(fā)現(xiàn)在系統(tǒng)開機之后况褪,就會有許多的Service進程被啟動撕贞,這個時候,而其很多實現(xiàn)都是通過native來實現(xiàn)的窝剖,這個時候如何調用麻掸,讓我們回歸到系統(tǒng)的啟動過程中酥夭。在Zygote進程中首先會調用啟動VM赐纱。

if?(startVm(&mJavaVM, &env, zygote) !=?0) {

?return;

}

onVmCreated(env);if?(startReg(env) <?0) {

return;

}

int?AndroidRuntime::startReg(JNIEnv* env)

{

if?(register_jni_procs(gRegJNI, NELEM(gRegJNI), env) <?0) {

env->PopLocalFrame(NULL);

return?-1;

}

....

return?0;

}

static?int?register_jni_procs(const?RegJNIRec?array[],?size_t?count, JNIEnv* env){

for?(size_t?i =?0; i < count; i++) {

if?(array[i].mProc(env) <?0) {

return?-1;

}

}

return?0;

}

static?const?RegJNIRec gRegJNI[] = {

REG_JNI(register_com_android_internal_os_RuntimeInit),

REG_JNI(register_android_os_SystemClock),

REG_JNI(register_android_util_EventLog),

REG_JNI(register_android_util_Log),

.....

}

array[i]是指gRegJNI數(shù)組, 該數(shù)組有100多個成員脊奋。其中每一項成員都是通過REG_JNI宏定義。

#define?REG_JNI(name){?

name?

}

struct?RegJNIRec?{

int?(*mProc)(JNIEnv*);

};

調用mProc疙描,就等價于調用其參數(shù)名所指向的函數(shù)诚隙。 例如REG_JNI(register_com_android_internal_os_RuntimeInit).mProc也就是指進入register_com_android_internal_os_RuntimeInit方法,進入這些方法之后起胰,就會是對于該類中的一些native方法和java方法的映射久又。

int?register_com_android_internal_os_RuntimeInit(JNIEnv* env)?{

return?jniRegisterNativeMethods(env,?"com/android/internal/os/RuntimeInit",

gMethods, NELEM(gMethods));

}

static?JNINativeMethod gMethods[] = {

{?"nativeFinishInit",?"()V",

(void*) com_android_internal_os_RuntimeInit_nativeFinishInit },

{?"nativeZygoteInit",?"()V",

(void*) com_android_internal_os_RuntimeInit_nativeZygoteInit },

{?"nativeSetExitWithoutCleanup",?"(Z)V",

(void*) com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup },

};

至此就完成了對于native方法和Java方法的映射關聯(lián)。

另一種加載方式

對于JNI方法的注冊無非是通過兩種方式一個是上述啟動過程中的注冊效五,一個是在程序中通過System.loadLibrary的方式進行注冊地消,這里,我們以System.loadLibrary來分析其注冊過程畏妖。

public?static?void?loadLibrary(String libname)?{

Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);

}

public?static?Runtime?getRuntime()?{

?return?currentRuntime;

}

synchronized?void?load0(Class fromClass, String filename)?{

if?(!(new?File(filename).isAbsolute())) {

throw?new?UnsatisfiedLinkError(

"Expecting an absolute path of the library: "?+ filename);

}

if?(filename ==?null) {

throw?new?NullPointerException("filename == null");

}

String error = doLoad(filename, fromClass.getClassLoader());

if?(error !=?null) {

throw?new?UnsatisfiedLinkError(error);

}

}

String librarySearchPath =?null;if?(loader !=?null?&& loader?instanceof?BaseDexClassLoader) {

BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;

librarySearchPath = dexClassLoader.getLdLibraryPath();

}

synchronized?(this) {

return?nativeLoad(name, loader, librarySearchPath);

}

經(jīng)過層層調用之后來到了nativeLoad方法脉执,這里對于這段代碼的分析,目的是為了了解戒劫,整個JNI的注冊過程和調用的時候半夷,JVM是如何找到相應的native方法的。

對于nativeLoad執(zhí)行的內(nèi)容迅细,會轉交到classLoader巫橄,最終會轉化為系統(tǒng)的調用,調用dlopen和dlsym函數(shù)茵典。

調用dlopen函數(shù)湘换,打開一個so文件并創(chuàng)建一個handle;

調用dlsym()函數(shù)统阿,查看相應so文件的JNI_OnLoad()函數(shù)指針枚尼,并執(zhí)行相應函數(shù)。

簡單的說砂吞,dlopen署恍、dlsym提供一種動態(tài)轉載庫到內(nèi)存的機制,在需要的時候蜻直,可以調用庫中的方法盯质。

在Java字節(jié)碼中,普通的方法是直接把字節(jié)碼放到code屬性表中概而,而native方法呼巷,與普通的方法通過一個標志“ACC_NATIVE”區(qū)分開來。java在執(zhí)行普通的方法調用的時候赎瑰,可以通過找方法表王悍,再找到相應的code屬性表,最終解釋執(zhí)行代碼餐曼。

在將動態(tài)庫load進來的時候压储,首先要做的第一步就是執(zhí)行該動態(tài)庫的JNI_OnLoad方法鲜漩,我們需要在該方法中聲明好native和java的關聯(lián),系統(tǒng)中的相關類因為沒有提供該方法集惋,因此需要手動調用了各自相應的注冊方法孕似。而在我們寫的demo中,編譯器則為我們做了這個操作刮刑,也不需要我們來做喉祭。寫好映射關系之后,調用registerNativeMethods方法來將這些方法進行注冊雷绢。具體的函數(shù)映射和注冊方式如上Runtime所示泛烙。

在編譯成的java代碼中,普通的Java方法會直接指向方法表中具體的方法翘紊,而對于native方法則是做了特殊的標記胶惰,在執(zhí)行到native方法時,就會根據(jù)我們之前加載進來的native的方法對應表中去查找相應的方法霞溪,然后執(zhí)行孵滞。

作者 |?Jensen_czx

地址 |?http://www.reibang.com/p/e6b9611f3045

聲明 |?本文是 Jensen_czx?原創(chuàng),已獲授權發(fā)布鸯匹,未經(jīng)原作者允許請勿轉載

閱讀原文

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坊饶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子殴蓬,更是在濱河造成了極大的恐慌匿级,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件染厅,死亡現(xiàn)場離奇詭異痘绎,居然都是意外死亡,警方通過查閱死者的電腦和手機肖粮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門孤页,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涩馆,你說我怎么就攤上這事行施。” “怎么了魂那?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵蛾号,是天一觀的道長。 經(jīng)常有香客問我涯雅,道長鲜结,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮精刷,結果婚禮上拗胜,老公的妹妹穿的比我還像新娘。我一直安慰自己贬养,他們只是感情好挤土,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布琴庵。 她就那樣靜靜地躺著误算,像睡著了一般。 火紅的嫁衣襯著肌膚如雪迷殿。 梳的紋絲不亂的頭發(fā)上儿礼,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音庆寺,去河邊找鬼蚊夫。 笑死,一個胖子當著我的面吹牛懦尝,可吹牛的內(nèi)容都是我干的知纷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼陵霉,長吁一口氣:“原來是場噩夢啊……” “哼琅轧!你這毒婦竟也來了?” 一聲冷哼從身側響起踊挠,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤乍桂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后效床,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睹酌,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年剩檀,在試婚紗的時候發(fā)現(xiàn)自己被綠了憋沿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡沪猴,死狀恐怖卤妒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情字币,我是刑警寧澤则披,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站洗出,受9級特大地震影響士复,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一阱洪、第九天 我趴在偏房一處隱蔽的房頂上張望便贵。 院中可真熱鬧,春花似錦冗荸、人聲如沸承璃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盔粹。三九已至,卻和暖如春程癌,著一層夾襖步出監(jiān)牢的瞬間舷嗡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工嵌莉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留进萄,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓锐峭,卻偏偏與公主長得像中鼠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沿癞,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容