深入理解Android-JNI

JNI概述

  • Java程序中的函數可以調用Native語言寫的函數冯遂,Native一般指的是C/C++寫的函數
  • Native層中的函數可以調用Java層的函數酬滤,也就是在C/C++程序中調用Java的函數
    要想做到以上兩點惰聂,JNI(Java Native Interface)技術應運而生。
    深入分析后可以知道,雖然Java代碼具有平臺無關性,但運行在具體平臺上的Java虛擬機并不能做到這一點蚕泽,但是JNI技術的出現屏蔽了不同平臺的差異,使得上層的Java具有了平臺無關的特性桥嗤。

MediaScanner的分析

android大量使用了JNI技術须妻,書中分析了MediaScanner這個例子。

  1. Java世界對應的是MediaScanner類泛领,這個類一些函數需要Native層來實現荒吏,比如對目錄的掃描。
  2. Native層對應是libmedia.so渊鞋,他完成了實際的功能绰更。
  3. JNI層對應的是libmedia_jni.so,這里可以看出JNI層其實也是Native代碼寫的锡宋。其中下劃線前的media是Native層庫的名字儡湾,這里指的就死libmedia庫,android對JNI庫的名字有規(guī)范员辩,一般是:
    lib模塊名_jni.so
  4. Java層的MediaScanner將通過libmedia_jni.so來與Native層的libmedia.so交互盒粮。

Java層的MediaScanner

framewor/base/media/java/android/media/MediaScanner.java是MediaScanner在Java層源碼的位置鸵鸥。

   public class MediaScanner
   {    
      static {
          /**加載對應的JNI庫奠滑,media_jni是對應JNI庫的名字,實際上加載動態(tài)庫的時候他會拓展成libmedia_jni.so **/        
          System.loadLibrary("media_jni");        
          native_init();   //調用native_init()函數妒穴,他是一個native函數
       }
      ......
      //掃面目錄的函數宋税,里面用到了processDirectory這個native函數
      public void scanDirectories(String[] directories, String volumeName) {
      ......
      for (int i = 0; i < directories.length; i++) { 
            processDirectory(directories[i], mClient);
      }
      ......
      private native void processDirectory(String path, MediaScannerClient client);
      private static native final void native_init();
      ......

這里說明了兩點內容,在需要native函數的java類中會包括JNI的加載和Native函數的聲明讼油,指示它由JNI層去完成杰赛。

  1. JNI加載流程
    Java層要調用Native函數,必須通過位于JNI層的動態(tài)庫才能實現矮台。動態(tài)庫的必須在運行時加載乏屯,所以,我們通常的做法就是在類的static語句中加入System.loadLibrary方法完成對動態(tài)庫的加載瘦赫,系統(tǒng)會自動將參數轉化成對應環(huán)境下真實動態(tài)庫的名字辰晕。linux上是so文件,win下就是dll文件确虱。
  2. Java的native函數
    加載完JNI庫后含友,我們只需要在Java中聲明由關鍵字native修飾的函數即可。是不是很簡單,但是JNI層的MediaScanner就沒那么輕松了窘问。

JNI層的MediaScanner

JNI層的MediaScanner代碼位于
frameworks/base/media/jni/android_media_MediaScanner.cpp
上面再Java層聲明的native_init和processFile在這里都有具體的實現辆童,那么問題就來了,Java層聲明的函數如何才能對應到JNI層的惠赫。

  1. 注冊JNI函數

native_init函數位于android.media這個包中把鉴,這樣可以得到它的全路徑名稱android.media.MediaScanner.native_init,之后再將.全部換成_汉形,native_init就找到了JNI層的實現纸镊。
上述就是JNI函數注冊的問題

  • 靜態(tài)注冊
    流程如下:


注意:因為packagename是該類完整的包名,所以javah命令需要在包外面執(zhí)行概疆,否則會找不到該類
靜態(tài)注冊的弊端:1逗威、需要編譯所有聲明了native函數的java類。2岔冀、javah生成的jni函數名會特別長凯旭。 3、初次調用native函數時要根據函數名來搜索對應JNI層的函數使套,并建立關聯罐呼,這會影響效率。

  • 動態(tài)注冊
    既然Java Native函數和JNI函數一一對應侦高,能不能有這種結構可以保存這種關聯關系呢嫉柴,肯定是有的,在JNI技術中有一種JNINativeMethod的結構奉呛,它記錄下了這些關聯關系计螺。其實Android大部分采用的是動態(tài)注冊的方法
    MediaScanner的JNI層中的示例:
    static const JNINativeMethod gMethods[] = {
    {
    "processDirectory",
    "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
    (void )android_media_MediaScanner_processDirectory
    },
    {
    "processFile",
    "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
    (void )android_media_MediaScanner_processFile
    },
    .......
    {
    "native_init",
    "()V",
    (void )android_media_MediaScanner_native_init
    },
    ......
    };
    基本結構定義:
    typedef struct {
    //java類中native函數的名字,不用攜帶包名瞧壮,例如“native_init”
    const char
    name;
    //java函數的簽名信息登馒,用字符串表示,是參數類型和返回值類型的組合
    const char
    signature咆槽;
    //JNI層對應函數的函數指針陈轿,它是void
    類型
    void* fnPtr;
    }JNINativeMethod秦忿;

    定義完畢之后麦射,需要執(zhí)行注冊

     int register_android_media_MediaScanner(JNIEnv *env){
        return AndroidRuntime::registerNativeMethods(env,  
             kClassMediaScanner, gMethods, NELEM(gMethods));
    }
    

    我們發(fā)現AndroidRunTime類提供了注冊函數,而它又調用了JNIHelp中的jniRegisterNativeMethods方法灯谣,最終會走到JNIEnv的RegisterNatives完成注冊工作潜秋。

那到底是什么時候去執(zhí)行這個?
在Java層執(zhí)行System.loadLibrary加載JNI動態(tài)庫后酬屉,緊接著會查找該庫中一個JNI_OnLoad的函數半等,如果有的話揍愁,動態(tài)注冊就在這里完成。然而在MediaScanner對應的android_media_MediaScanner.cpp中并沒有發(fā)現這個函數杀饵。由于多媒體系統(tǒng)中很對地方用到JNI莽囤,所以register_android_media_MediaScanner這個注冊方法被放在了android_media_MediaPlayer.cpp的JNI_OnLoad方法中,當然還有其它的多媒體相關的注冊函數切距。

數據類型轉換

主要解決的問題是:在Java中調用native函數傳遞的參數是Java數據類型朽缎,如何將他們轉化成Native的數據類型。
Java分基本數據類型和引用數據類型谜悟,先看基本數據類型:

Java Native類型 符號屬性 字長
boolean jboolean 無符號 8位
byte jbyte 無符號 8位
char jchar 無符號 16位
short jshort 有符號 16位
int jint 有符號 32位
long jlong 有符號 64位
float jfloat 有符號 32位
double jdouble 有符號 64位

這里要注意的是轉換成Native類型后對應的數據類型的字長话肖,char在Java中是占兩個字節(jié),jchar同樣也是葡幸,要區(qū)別于普通char只占一個字節(jié)最筒。

引用類型的轉換

Java引用類型 Native類型 Java引用類型 Native類型
All object jobject char[] jcharArray
java.lang.Class實例 jclass short[] jshortArray
java.lang.String實例 jstring int[] jintArray
Object[] jobjectArray long[] jlongArray
boolean[] jbooleanArray flaot[] jflaotArray
byte[] jbyteArray double[] jdoubleArray
java.lang.Throwable實例 jthrowable

再拿MediaScanner舉例,其中的processFile
android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){
Java層的函數中只傳了三個參數蔚叨,即path床蜘,mineType和client,這里它們的類型都轉化成了native類型蔑水。前兩個參數分別是JNIEnv和Java層的MediaScanner對象(jobject)

JNIEnv

JNIEnv其實是一個與線程相關的代表JNI環(huán)境的結構體邢锯。


JNIEnv提供了JNI相關的系統(tǒng)函數,這些函數可以做到:調用Java函數和操作jobject對象等搀别。

  1. 通過JNIEnv操作jobject
    在JNI規(guī)則中丹擎,用jfiledID和jMethodID來表示java類的成員變量和成員函數。比如MyMediaScannerClient中歇父。
    代碼中將scanFile和handleStringTag函數的jmethodID保存為MyMediaScannerClient的成員變量蒂培,這是為了解決每次操作object都會去查詢jmethodID或jfieldID而帶來的運行效率降低。
    使用可以看virtual status_t scanFile中的mEnv->CallVoidMethod
    完成JNI層調用Java對象的函數庶骄。
  2. jstring
    JNIEnv提供有關jstring的函數
  3. JNI簽名介紹
    Java函數支持函數重載毁渗,也就是說践磅,可以定義同名但是不同參數的函數单刁,但僅僅根據函數名是沒法找到具體函數的。為了解決這個問題府适,JNI技術就將參數類型和返回值類型的組合作為一個函數的簽名信息羔飞,有了這個簽名和函數名,就能很順利的找到Java中的函數檐春。
  4. 垃圾回收
    JNI的三種類型引用技術:
    local reference:本地引用逻淌,一旦JNI函數返回時,jobject就可能會被回收
    Global reference:全局引用疟暖,不主動釋放不會被回收
    Weak Global reference:特殊的全局引用卡儒,可能會被回收田柔,每次使用時需要判斷是否回收
  5. JNI中的異常處理



    JNIEnv提供了三個函數給予幫助
    ExceptionOccured函數,用來判斷是否發(fā)生異常
    ExceptionClear函數骨望,用來清理JNI層發(fā)生的異常硬爆。
    ThrownNew函數,用來Java層拋出異常擎鸠。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末缀磕,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子劣光,更是在濱河造成了極大的恐慌袜蚕,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绢涡,死亡現場離奇詭異牲剃,居然都是意外死亡,警方通過查閱死者的電腦和手機雄可,發(fā)現死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門颠黎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滞项,你說我怎么就攤上這事狭归。” “怎么了文判?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵过椎,是天一觀的道長。 經常有香客問我戏仓,道長疚宇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任赏殃,我火速辦了婚禮敷待,結果婚禮上,老公的妹妹穿的比我還像新娘仁热。我一直安慰自己榜揖,他們只是感情好,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布抗蠢。 她就那樣靜靜地躺著举哟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪迅矛。 梳的紋絲不亂的頭發(fā)上妨猩,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音秽褒,去河邊找鬼壶硅。 笑死威兜,一個胖子當著我的面吹牛,可吹牛的內容都是我干的庐椒。 我是一名探鬼主播牡属,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扼睬!你這毒婦竟也來了逮栅?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤窗宇,失蹤者是張志新(化名)和其女友劉穎措伐,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體军俊,經...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡侥加,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了粪躬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片担败。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖镰官,靈堂內的尸體忽然破棺而出提前,到底是詐尸還是另有隱情,我是刑警寧澤泳唠,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布狈网,位于F島的核電站,受9級特大地震影響笨腥,放射性物質發(fā)生泄漏拓哺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一脖母、第九天 我趴在偏房一處隱蔽的房頂上張望士鸥。 院中可真熱鬧,春花似錦谆级、人聲如沸烤礁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸽凶。三九已至币砂,卻和暖如春建峭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背决摧。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工亿蒸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凑兰,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓边锁,卻偏偏與公主長得像姑食,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茅坛,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內容