深入理解Android-JNI

JNI概述

  • Java程序中的函數(shù)可以調(diào)用Native語言寫的函數(shù),Native一般指的是C/C++寫的函數(shù)
  • Native層中的函數(shù)可以調(diào)用Java層的函數(shù),也就是在C/C++程序中調(diào)用Java的函數(shù)
    要想做到以上兩點,JNI(Java Native Interface)技術(shù)應(yīng)運(yùn)而生。
    深入分析后可以知道,雖然Java代碼具有平臺無關(guān)性,但運(yùn)行在具體平臺上的Java虛擬機(jī)并不能做到這一點,但是JNI技術(shù)的出現(xiàn)屏蔽了不同平臺的差異,使得上層的Java具有了平臺無關(guān)的特性。

MediaScanner的分析

android大量使用了JNI技術(shù),書中分析了MediaScanner這個例子。

  1. Java世界對應(yīng)的是MediaScanner類,這個類一些函數(shù)需要Native層來實現(xiàn),比如對目錄的掃描。
  2. Native層對應(yīng)是libmedia.so,他完成了實際的功能。
  3. JNI層對應(yīng)的是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 {
          /**加載對應(yīng)的JNI庫,media_jni是對應(yīng)JNI庫的名字,實際上加載動態(tài)庫的時候他會拓展成libmedia_jni.so **/        
          System.loadLibrary("media_jni");        
          native_init();   //調(diào)用native_init()函數(shù),他是一個native函數(shù)
       }
      ......
      //掃面目錄的函數(shù),里面用到了processDirectory這個native函數(shù)
      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();
      ......

這里說明了兩點內(nèi)容,在需要native函數(shù)的java類中會包括JNI的加載和Native函數(shù)的聲明,指示它由JNI層去完成。

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

JNI層的MediaScanner

JNI層的MediaScanner代碼位于
frameworks/base/media/jni/android_media_MediaScanner.cpp
上面再Java層聲明的native_init和processFile在這里都有具體的實現(xiàn),那么問題就來了,Java層聲明的函數(shù)如何才能對應(yīng)到JNI層的。

  1. 注冊JNI函數(shù)

native_init函數(shù)位于android.media這個包中,這樣可以得到它的全路徑名稱android.media.MediaScanner.native_init,之后再將.全部換成_,native_init就找到了JNI層的實現(xiàn)。
上述就是JNI函數(shù)注冊的問題

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


注意:因為packagename是該類完整的包名,所以javah命令需要在包外面執(zhí)行,否則會找不到該類
靜態(tài)注冊的弊端:1、需要編譯所有聲明了native函數(shù)的java類。2、javah生成的jni函數(shù)名會特別長。 3、初次調(diào)用native函數(shù)時要根據(jù)函數(shù)名來搜索對應(yīng)JNI層的函數(shù),并建立關(guān)聯(lián),這會影響效率。

  • 動態(tài)注冊
    既然Java Native函數(shù)和JNI函數(shù)一一對應(yīng),能不能有這種結(jié)構(gòu)可以保存這種關(guān)聯(lián)關(guān)系呢,肯定是有的,在JNI技術(shù)中有一種JNINativeMethod的結(jié)構(gòu),它記錄下了這些關(guān)聯(lián)關(guān)系。其實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
    },
    ......
    };
    基本結(jié)構(gòu)定義:
    typedef struct {
    //java類中native函數(shù)的名字,不用攜帶包名,例如“native_init”
    const char
    name;
    //java函數(shù)的簽名信息,用字符串表示,是參數(shù)類型和返回值類型的組合
    const char
    signature;
    //JNI層對應(yīng)函數(shù)的函數(shù)指針,它是void
    類型
    void* fnPtr;
    }JNINativeMethod;

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

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

    我們發(fā)現(xiàn)AndroidRunTime類提供了注冊函數(shù),而它又調(diào)用了JNIHelp中的jniRegisterNativeMethods方法,最終會走到JNIEnv的RegisterNatives完成注冊工作。

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

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

主要解決的問題是:在Java中調(diào)用native函數(shù)傳遞的參數(shù)是Java數(shù)據(jù)類型,如何將他們轉(zhuǎn)化成Native的數(shù)據(jù)類型。
Java分基本數(shù)據(jù)類型和引用數(shù)據(jù)類型,先看基本數(shù)據(jù)類型:

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位

這里要注意的是轉(zhuǎn)換成Native類型后對應(yīng)的數(shù)據(jù)類型的字長,char在Java中是占兩個字節(jié),jchar同樣也是,要區(qū)別于普通char只占一個字節(jié)。

引用類型的轉(zhuǎn)換

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層的函數(shù)中只傳了三個參數(shù),即path,mineType和client,這里它們的類型都轉(zhuǎn)化成了native類型。前兩個參數(shù)分別是JNIEnv和Java層的MediaScanner對象(jobject)

JNIEnv

JNIEnv其實是一個與線程相關(guān)的代表JNI環(huán)境的結(jié)構(gòu)體。


JNIEnv提供了JNI相關(guān)的系統(tǒng)函數(shù),這些函數(shù)可以做到:調(diào)用Java函數(shù)和操作jobject對象等。

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



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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容