人在旅途

朝着胜利的方向~

android JNI

1.  從如何載入*.so檔案談起

    由於Android的應用層級類別都是以Java撰寫的,這些Java類別轉譯為Dex型式的Bytecode之後,必須仰賴Dalvik虛擬機器(VM: Virtual Machine)來執行之。VMAndroid平台裡,扮演很重要的角色。

    此外,在執行Java類別的過程中,如果Java類別需要與C組件溝通時,VM就會去載入C組件,然後讓Java的函數順利地呼叫到C組件的函數。此時,VM扮演著橋樑的角色,讓JavaC組件能透過標準的JNI介面而相互溝通。

    應用層級的Java類別是在虛擬機器(VM: Vitual Machine)上執行的,而C組件不是在VM上執行,那麼Java程式又如何要求VM去載入(Load)所指定的C組件呢? 可使用下述指令:

     System.loadLibrary(*.so的檔名);

 

例如,在上一節的範例裡的NativeJniAdder類別,其程式碼:

 

 看看 Android框架裡所提供的MediaPlayer.java類別,內含指令:

     public class MediaPlayer{    

    static {

        System.loadLibrary("media_jni");

    }

 ……..

}

      

這要求VM去載入Android/system/lib/libmedia_jni.so檔案。載入*.so檔之後,Java類別與*.so檔就匯合起來,一起執行了。

 

 

2.  如何撰寫*.so的入口函數

    ---- JNI_OnLoad()JNI_OnUnload()函數之用途

 

    VM執行到System.loadLibrary()函數時,首先會去執行C組件裡的JNI_OnLoad()函數。它的用途有二:

1.       告訴VMC組件使用那一個JNI版本。如果你的*.so檔沒有提供JNI_OnLoad()函數,VM會默認該*.so檔是使用最老的JNI 1.1版本。由於新版的JNI做了許多擴充,如果需要使用JNI的新版功能,例如JNI 1.4 java.nio.ByteBuffer, 就必須藉由JNI_OnLoad()函數來告知VM

2.       由於VM執行到System.loadLibrary()函數時,就會立即先呼叫JNI_OnLoad(),所以C組件的開發者可以藉由JNI_OnLoad()來進行C組件內的初期值之設定(Initialization)

例如,在Android/system/lib/libmedia_jni.so檔案裡,就提供了JNI_OnLoad()函數,其程式碼片段為:

//#define LOG_NDEBUG 0

#define LOG_TAG "MediaPlayer-JNI"

………

jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

    JNIEnv* env = NULL;

    jint result = -1;

 

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

        LOGE("ERROR: GetEnv failed/n");

        goto bail;

    }

    assert(env != NULL);

    if (register_android_media_MediaPlayer(env) < 0) {

        LOGE("ERROR: MediaPlayer native registration failed/n");

        goto bail;

    }

    if (register_android_media_MediaRecorder(env) < 0) {

        LOGE("ERROR: MediaRecorder native registration failed/n");

        goto bail;

    }

    if (register_android_media_MediaScanner(env) < 0) {

        LOGE("ERROR: MediaScanner native registration failed/n");

        goto bail;

    }

    if (register_android_media_MediaMetadataRetriever(env) < 0) {

        LOGE("ERROR: MediaMetadataRetriever native registration failed/n");

        goto bail;

    }

    /* success -- return valid version number */

    result = JNI_VERSION_1_4;

bail:

    return result;

}

// KTHXBYE

 

    此函數回傳JNI_VERSION_1_4值給VM,於是VM知道了其所使用的JNI版本了。此外,它也做了一些初期的動作(可呼叫任何本地函數),例如指令:

   if (register_android_media_MediaPlayer(env) < 0) {

        LOGE("ERROR: MediaPlayer native registration failed/n");

        goto bail;

    }

就將此組件提供的各個本地函數(Native Function)登記到VM裡,以便能加快後續呼叫本地函數之效率。

    JNI_OnUnload()函數JNI_OnLoad()相對應的。在載入C組件時會立即呼叫JNI_OnLoad()來進行組件內的初期動作;而當VM釋放該C組件時,則會呼叫JNI_OnUnload()函數來進行善後清除動作。當VM呼叫JNI_OnLoad()JNI_Unload()函數時,都會將VM的指標(Pointer)傳遞給它們,其參數如下:

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

      ………

     }

jint JNI_OnUnload(JavaVM* vm, void* reserved){

      ………

     }

JNI_OnLoad()函數裡,就透過VM之指標而取得JNIEnv之指標值,並存入env指標變數裡,如下述指令:

jint JNI_OnLoad(JavaVM* vm, void* reserved){

     JNIEnv* env = NULL;

     jint result = -1;

 

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {

          LOGE("ERROR: GetEnv failed/n");

          goto bail;

    }

    }

由於VM通常是多執行緒(Multi-threading)的執行環境。每一個執行緒在呼叫JNI_OnLoad()時,所傳遞進來的JNIEnv指標值都是不同的。為了配合這種多執行緒的環境,C組件開發者在撰寫本地函數時,可藉由JNIEnv指標值之不同而避免執行緒的資料衝突問題,才能確保所寫的本地函數能安全地在Android多執行緒VM 裡安全地執行。基於這個理由,當在呼叫C組件的函數時,都會將JNIEnv指標值傳遞給它,如下:

jint JNI_OnLoad(JavaVM* vm, void* reserved)

{

JNIEnv* env = NULL;

  ……….

    if (register_android_media_MediaPlayer(env) < 0) {

          ……….

   }

       }

JNI_OnLoad()呼叫register_android_media_MediaPlayer(env)函數時,就將env指標值傳遞過去。如此,在register_android_media_MediaPlayer()函數就能藉由該指標值而區別不同的執行緒,以便化解資料衝突的問題。

例如,在register_android_media_MediaPlayer()函數裡,可撰寫下述指令:

       if ((*env)->MonitorEnter(env, obj) != JNI_OK) {

      ……….

}

    查看是否已經有其他執行緒進入此物件,如果沒有,此執行緒就進入該物件裡執行了。還有,也可撰寫下述指令:

       if ((*env)->MonitorExit(env, obj) != JNI_OK) {

            ………

        }

查看是否此執行緒正在此物件內執行,如果是,此執行緒就會立即離開

 

 

3registerNativeMethods()函數之用途

    應用層級的Java類別透過VM而呼叫到本地函數。一般是仰賴VM去尋找*.so裡的本地函數。如果需要連續呼叫很多次,每次都需要尋找一遍,會多花許多時間。此時,組件開發者可以自行將本地函數向VM進行登記。例如,Android/system/lib/libmedia_jni.so檔案裡的程式碼片段如下:

 

//#define LOG_NDEBUG 0

#define LOG_TAG "MediaPlayer-JNI"

………

static JNINativeMethod gMethods[] = {

    {"setDataSource",   "(Ljava/lang/String;)V",

  (void *)android_media_MediaPlayer_setDataSource},

    {"setDataSource",    "(Ljava/io/FileDescriptor;JJ)V", 

(void *)android_media_MediaPlayer_setDataSourceFD},

    {"prepare",   "()V",     (void *)android_media_MediaPlayer_prepare},

    {"prepareAsync",   "()V",   (void *)android_media_MediaPlayer_prepareAsync},

    {"_start",   "()V",   (void *)android_media_MediaPlayer_start},

    {"_stop",   "()V",    (void *)android_media_MediaPlayer_stop},

    {"getVideoWidth",   "()I",      (void *)android_media_MediaPlayer_getVideoWidth},

    {"getVideoHeight",   "()I",      (void *)android_media_MediaPlayer_getVideoHeight},

    {"seekTo",              "(I)V",      (void *)android_media_MediaPlayer_seekTo},

    {"_pause",              "()V",       (void *)android_media_MediaPlayer_pause},

    {"isPlaying",           "()Z",       (void *)android_media_MediaPlayer_isPlaying},

    {"getCurrentPosition",  "()I",  (void *)android_media_MediaPlayer_getCurrentPosition},

    {"getDuration",         "()I",     (void *)android_media_MediaPlayer_getDuration},

    {"_release",            "()V",      (void *)android_media_MediaPlayer_release},

    {"_reset",              "()V",        (void *)android_media_MediaPlayer_reset},

    {"setAudioStreamType",  "(I)V", (void *)android_media_MediaPlayer_setAudioStreamType},

    {"setLooping",      "(Z)V",      (void *)android_media_MediaPlayer_setLooping},

    {"setVolume",      "(FF)V",     (void *)android_media_MediaPlayer_setVolume},

    {"getFrameAt",     "(I)Landroid/graphics/Bitmap;", 

  (void *)android_media_MediaPlayer_getFrameAt},

    {"native_setup",    "(Ljava/lang/Object;)V", 

           (void *)android_media_MediaPlayer_native_setup},

    {"native_finalize",     "()V",    (void *)android_media_MediaPlayer_native_finalize},

};

………

static int register_android_media_MediaPlayer(JNIEnv *env){

  ………

    return AndroidRuntime::registerNativeMethods(env,

                "android/media/MediaPlayer", gMethods, NELEM(gMethods));

}

……….

//

jint JNI_OnLoad(JavaVM* vm, void* reserved){

  ………

    if (register_android_media_MediaPlayer(env) < 0) {

        LOGE("ERROR: MediaPlayer native registration failed/n");

        goto bail;

    }

  ……….

}

VM載入libmedia_jni.so檔案時,就呼叫JNI_OnLoad()函數。接著,JNI_OnLoad()呼叫register_android_media_MediaPlayer()函數。此時,就呼叫到AndroidRuntime::registerNativeMethods()函數,向VM(AndroidRuntime)登記gMethods[]表格所含的本地函數了。簡而言之,registerNativeMethods()函數的用途有二:

1.            更有效率去找到函數。

2.            可在執行期間進行抽換。由於gMethods[]是一個<名稱,函數指標>對照表,在程式執行時,可多次呼叫registerNativeMethods()函數來更換本地函數之指標,而達到彈性抽換本地函數之目的。

阅读更多
个人分类: Android(转)
想对作者说点什么? 我来说一句

android 应用\JNI 技术

2011年05月24日 211KB 下载

Android NDK JNI 经典实例

2011年04月14日 85KB 下载

android JNI 学习笔记.doc

2012年03月03日 37KB 下载

Android 2.3截屏JNI代码

2011年11月24日 2KB 下载

android ndk demo

2018年06月07日 11.89MB 下载

JNI完全技术手册

2015年06月18日 38.5MB 下载

Android JNI 实例

2017年08月30日 111KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭