JNI技術(shù)簡介

JNI(Java Native Interface)

提供一種Java字節(jié)碼調(diào)用C/C++的解決方案秀睛,JNI描述的是一種技術(shù)。


NDK(Native Development Kit)

Android NDK 是一組允許您將 C 或 C++(“原生代碼”)嵌入到 Android 應(yīng)用中的工具叛甫,NDK描述的是工具集。 能夠在 Android 應(yīng)用中使用原生代碼對于想執(zhí)行以下一項(xiàng)或多項(xiàng)操作的開發(fā)者特別有用:

  • 在平臺之間移植其應(yīng)用应媚。
  • 重復(fù)使用現(xiàn)有庫弥雹,或者提供其自己的庫供重復(fù)使用。
  • 在某些情況下提高性能园担,特別是像游戲這種計(jì)算密集型應(yīng)用届谈。

JNI方法注冊

靜態(tài)注冊

當(dāng)Java層調(diào)用navtie函數(shù)時(shí),會在JNI庫中根據(jù)函數(shù)名查找對應(yīng)的JNI函數(shù)弯汰。如果沒找到艰山,會報(bào)錯。如果找到了咏闪,則會在native函數(shù)與JNI函數(shù)之間建立關(guān)聯(lián)關(guān)系曙搬,其實(shí)就是保存JNI函數(shù)的函數(shù)指針。下次再調(diào)用native函數(shù)鸽嫂,就可以直接使用這個(gè)函數(shù)指針纵装。

  1. JNI函數(shù)名格式(需將”.”改為”_”):

Java_ + 包名(com.example.auto.jnitest)+ 類名(MainActivity) + 函數(shù)名(stringFromJNI)

  1. 靜態(tài)方法的缺點(diǎn):
  • 要求JNI函數(shù)的名字必須遵循JNI規(guī)范的命名格式;
  • 名字冗長据某,容易出錯橡娄;
  • 初次調(diào)用會根據(jù)函數(shù)名去搜索JNI中對應(yīng)的函數(shù),會影響執(zhí)行效率癣籽;
  • 需要編譯所有聲明了native函數(shù)的Java類挽唉,每個(gè)所生成的class文件都要用javah工具生成一個(gè)頭文件;

動態(tài)注冊

通過提供一個(gè)函數(shù)映射表筷狼,注冊給JVM虛擬機(jī)瓶籽,這樣JVM就可以用函數(shù)映射表來調(diào)用相應(yīng)的函數(shù),就不必通過函數(shù)名來查找需要調(diào)用的函數(shù)埂材。

  1. Java與JNI通過JNINativeMethod的結(jié)構(gòu)來建立函數(shù)映射表塑顺,它在jni.h頭文件中定義,其結(jié)構(gòu)內(nèi)容如下:
typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;
  1. 創(chuàng)建映射表后俏险,調(diào)用RegisterNatives函數(shù)將映射表注冊給JVM;
  2. 當(dāng)Java層通過System.loadLibrary加載JNI庫時(shí)严拒,會在庫中查JNI_OnLoad函數(shù)∈溃可將JNI_OnLoad視為JNI庫的入口函數(shù)糙俗,需要在這里完成所有函數(shù)映射和動態(tài)注冊工作,及其他一些初始化工作预鬓。

數(shù)據(jù)類型轉(zhuǎn)換

基礎(chǔ)數(shù)據(jù)類型轉(zhuǎn)換

引用數(shù)據(jù)類型轉(zhuǎn)換

除了Class巧骚、String、Throwable和基本數(shù)據(jù)類型的數(shù)組外格二,其余所有Java對象的數(shù)據(jù)類型在JNI中都用jobject表示劈彪。Java中的String也是引用類型,但是由于使用頻率較高顶猜,所以在JNI中單獨(dú)創(chuàng)建了一個(gè)jstring類型沧奴。

  • 引用類型不能直接在 Native 層使用,需要根據(jù) JNI 函數(shù)進(jìn)行類型的轉(zhuǎn)化后长窄,才能使用;
  • 多維數(shù)組(含二維數(shù)組)都是引用類型滔吠,需要使用 jobjectArray 類型存取其值纲菌;
    例如,二維整型數(shù)組就是指向一位數(shù)組的數(shù)組疮绷,其聲明使用方式如下:
    //獲得一維數(shù)組的類引用翰舌,即jintArray類型  
    jclass intArrayClass = env->FindClass("[I");   
    //構(gòu)造一個(gè)指向jintArray類一維數(shù)組的對象數(shù)組,該對象數(shù)組初始大小為length冬骚,類型為 jsize
    jobjectArray obejctIntArray  =  env->NewObjectArray(length ,intArrayClass , NULL);

JNI函數(shù)簽名信息

由于Java支持函數(shù)重載椅贱,因此僅僅根據(jù)函數(shù)名是沒法找到對應(yīng)的JNI函數(shù)。為了解決這個(gè)問題只冻,JNI將參數(shù)類型和返回值類型作為函數(shù)的簽名信息庇麦。

  1. JNI規(guī)范定義的函數(shù)簽名信息格式:
    (參數(shù)1類型字符…)返回值類型字符

  2. 函數(shù)簽名例子:
  3. JNI常用的數(shù)據(jù)類型及對應(yīng)字符:

JNIEnv介紹

  1. JNIEnv概念 :
    JNIEnv是一個(gè)線程相關(guān)的結(jié)構(gòu)體, 該結(jié)構(gòu)體代表了 Java 在本線程的運(yùn)行環(huán)境。通過JNIEnv可以調(diào)用到一系列JNI系統(tǒng)函數(shù)喜德。

  2. JNIEnv線程相關(guān)性:
    每個(gè)線程中都有一個(gè) JNIEnv 指針山橄。JNIEnv只在其所在線程有效, 它不能在線程之間進(jìn)行傳遞。

注意:在C++創(chuàng)建的子線程中獲取JNIEnv舍悯,要通過調(diào)用JavaVM的AttachCurrentThread函數(shù)獲得驾胆。在子線程退出時(shí),要調(diào)用JavaVM的DetachCurrentThread函數(shù)來釋放對應(yīng)的資源贱呐,否則會出錯丧诺。

  1. JNIEnv 作用:
    • 訪問Java成員變量和成員方法;
    • 調(diào)用Java構(gòu)造方法創(chuàng)建Java對象等奄薇。

JNI編譯

ndkBuild

使用ndk-build編譯生成so文件

Cmake編譯

CMake 則是一個(gè)跨平臺的編譯工具驳阎,它并不會直接編譯出對象,而是根據(jù)自定義的語言規(guī)則(CMakeLists.txt)生成 對應(yīng) makefile 或 project 文件馁蒂,然后再調(diào)用底層的編譯呵晚, 在Android Studio 2.2 之后支持Cmake編譯。

  • add_library 指令
    語法:add_library(libname [SHARED | STATIC | MODULE] [EXCLUDE_FROM_ALL] [source])
    將一組源文件 source 編譯出一個(gè)庫文件沫屡,并保存為 libname.so (lib 前綴是生成文件時(shí) CMake自動添加上去的)饵隙。其中有三種庫文件類型,不寫的話沮脖,默認(rèn)為 STATIC;
    • SHARED: 表示動態(tài)庫金矛,可以在(Java)代碼中使用 System.loadLibrary(name) 動態(tài)調(diào)用;
    • STATIC: 表示靜態(tài)庫勺届,集成到代碼中會在編譯時(shí)調(diào)用驶俊;
    • MODULE: 只有在使用 dyId 的系統(tǒng)有效,如果不支持 dyId免姿,則被當(dāng)作 SHARED 對待饼酿;
    • EXCLUDE_FROM_ALL: 表示這個(gè)庫不被默認(rèn)構(gòu)建,除非其他組件依賴或手工構(gòu)建;
#將compress.c 編譯成 libcompress.so 的共享庫
add_library(compress SHARED compress.c)
  • target_link_libraries 指令
    語法:target_link_libraries(target library <debug | optimized> library2…)
    這個(gè)指令可以用來為 target 添加需要的鏈接的共享庫,同樣也可以用于為自己編寫的共享庫添加共享庫鏈接故俐。如:
#指定 compress 工程需要用到 libjpeg 庫和 log 庫
target_link_libraries(compress libjpeg ${log-lib})
  • find_library 指令
    語法:find_library(<VAR> name1 path1 path2 ...)
    VAR 變量表示找到的庫全路徑想鹰,包含庫文件名 。例如:
find_library(libX  X11 /usr/lib)
find_library(log-lib log)  #路徑為空药版,應(yīng)該是查找系統(tǒng)環(huán)境變量路徑

Android NDK 開發(fā):CMake 使用

Abi架構(gòu)

ABI(Application binary interface)應(yīng)用程序二進(jìn)制接口辑舷。不同的CPU 與指令集的每種組合都有定義的 ABI (應(yīng)用程序二進(jìn)制接口),一段程序只有遵循這個(gè)接口規(guī)范才能在該 CPU 上運(yùn)行刚陡,所以同樣的程序代碼為了兼容多個(gè)不同的CPU,需要為不同的 ABI 構(gòu)建不同的庫文件株汉。當(dāng)然對于CPU來說筐乳,不同的架構(gòu)并不意味著一定互不兼容。

  • armeabi設(shè)備只兼容armeabi乔妈;
  • armeabi-v7a設(shè)備兼容armeabi-v7a蝙云、armeabi;
  • arm64-v8a設(shè)備兼容arm64-v8a路召、armeabi-v7a勃刨、armeabi;
  • X86設(shè)備兼容X86股淡、armeabi身隐;
  • X86_64設(shè)備兼容X86_64、X86唯灵、armeabi贾铝;
  • mips64設(shè)備兼容mips64、mips埠帕;
  • mips只兼容mips垢揩;

根據(jù)以上的兼容總結(jié),我們還可以得到一些規(guī)律:

  • armeabi的SO文件基本上可以說是萬金油敛瓷,它能運(yùn)行在除了mips和mips64的設(shè)備上叁巨,但在非armeabi設(shè)備上運(yùn)行性能還是有所損耗;
  • 64位的CPU架構(gòu)總能向下兼容其對應(yīng)的32位指令集呐籽,如:x86_64兼容X86锋勺,arm64-v8a兼容armeabi-v7a,mips64兼容mips狡蝶;

問題排查 addr2line

03-21 23:59:32.032 6770-6770/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
03-21 23:59:32.032 6770-6770/? A/DEBUG: Build fingerprint: 'google/sdk_gphone_x86/generic_x86:8.1.0/OPM1.171004.001/4376136:user/release-keys'
03-21 23:59:32.032 6770-6770/? A/DEBUG: Revision: '0'
03-21 23:59:32.032 6770-6770/? A/DEBUG: ABI: 'x86'
03-21 23:59:32.032 6770-6770/? A/DEBUG: pid: 6745, tid: 6745, name: ucai.nativedemo  >>> com.choufucai.nativedemo <<<
03-21 23:59:32.032 6770-6770/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x70
03-21 23:59:32.032 6770-6770/? A/DEBUG: Cause: null pointer dereference
03-21 23:59:32.032 6770-6770/? A/DEBUG:     eax 00000070  ebx a8a6479c  ecx 00000035  edx 00000075
03-21 23:59:32.032 6770-6770/? A/DEBUG:     esi ffffffff  edi ffffffff
03-21 23:59:32.032 6770-6770/? A/DEBUG:     xcs 00000073  xds 0000007b  xes 0000007b  xfs 0000003b  xss 0000007b
03-21 23:59:32.032 6770-6770/? A/DEBUG:     eip a89a2553  ebp bffa2408  esp bffa1e78  flags 00010202
03-21 23:59:32.228 6770-6770/? A/DEBUG: backtrace:
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #00 pc 0001d553  /system/lib/libc.so (strlen+51)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #01 pc 0005fd5d  /system/lib/libc.so (__vfprintf+5581)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #02 pc 0008439e  /system/lib/libc.so (vsnprintf+222)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #03 pc 00022f30  /system/lib/libc.so (__vsnprintf_chk+48)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #04 pc 000068de  /system/lib/liblog.so (__android_log_print+78)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #05 pc 00000ee2  /data/app/com.choufucai.nativedemo-c_F0BwkNYJA0ITdueTXEdg==/lib/x86/libnative-lib.so
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #06 pc 00647e67  /system/lib/libart.so (art_quick_generic_jni_trampoline+71)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #07 pc 00641e62  /system/lib/libart.so (art_quick_invoke_stub+338)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #08 pc 00115fdf  /system/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+223)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #09 pc 0032143f  /system/lib/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+335)
03-21 23:59:32.228 6770-6770/? A/DEBUG:     #10 pc 0031a6a4  /system/lib/libart.so 

以上錯誤日志中backtrace就是堆棧信息宙刘,#00 #01 就是堆棧列表。 #00 就是堆棧頂層即是錯誤所在地址牢酵,pc后面的就是地址悬包,可以通過以下命令查找出地址可以獲得對應(yīng)的源碼文件和行號:

// -f 輸出函數(shù)名  
// -e 輸出錯誤代碼行數(shù)和文件路徑  
// xxx.so 對應(yīng)出錯的so文件, 在android工程obj目錄下  
// addr 是具體的地址  
arm-linux-androideabi-addr2line -f -e xxx.so addr 

獨(dú)立工具鏈

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市馍乙,隨后出現(xiàn)的幾起案子布近,更是在濱河造成了極大的恐慌垫释,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撑瞧,死亡現(xiàn)場離奇詭異棵譬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)预伺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門订咸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酬诀,你說我怎么就攤上這事脏嚷。” “怎么了瞒御?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵父叙,是天一觀的道長。 經(jīng)常有香客問我肴裙,道長趾唱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任蜻懦,我火速辦了婚禮甜癞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宛乃。我一直安慰自己带欢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布烤惊。 她就那樣靜靜地躺著乔煞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柒室。 梳的紋絲不亂的頭發(fā)上渡贾,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音雄右,去河邊找鬼空骚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛擂仍,可吹牛的內(nèi)容都是我干的囤屹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼逢渔,長吁一口氣:“原來是場噩夢啊……” “哼肋坚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤智厌,失蹤者是張志新(化名)和其女友劉穎诲泌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铣鹏,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡敷扫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诚卸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葵第。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖合溺,靈堂內(nèi)的尸體忽然破棺而出卒密,到底是詐尸還是另有隱情,我是刑警寧澤辫愉,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布栅受,位于F島的核電站将硝,受9級特大地震影響恭朗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜依疼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一痰腮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧律罢,春花似錦膀值、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巾钉,卻和暖如春翘狱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砰苍。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工潦匈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赚导。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓茬缩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吼旧。 傳聞我的和親對象是個(gè)殘疾皇子凰锡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

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