認識*.so里的JNI_OnLoad()函數(shù) 當(dāng)Android的 VM(Virtual
Machine)執(zhí)行到C組件(即*so文件)里的System.loadLibrary()函數(shù)時,首先會去執(zhí)行C組件里的JNI_OnLoad()函數(shù)。它的用途有二: 1. 告訴VM此C組件使用那一個JNI版本。如果你的*.so文件沒有提供JNI_OnLoad()函數(shù),VM會默認該*.so檔是使用最老的JNI 1.1版本。由于新版的JNI做了許多擴充,如果需要使用JNI的新版功能,例如JNI 1.4的 java.nio.ByteBuffer,
就必須藉由JNI_OnLoad()函數(shù)來告知VM。 2.
由于VM執(zhí)行到System.loadLibrary()函數(shù)時,就會立即先呼叫JNI_OnLoad(),所以C組件的開發(fā)者可以藉由JNI_OnLoad()來進行C組件內(nèi)的初期值之設(shè)定(Initialization)。
例如,在Android的/system/lib/libmedia_jni.so檔案里,就提供了JNI_OnLoad()函數(shù),其程序代碼片段為:
//#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
此函數(shù)回傳JNI_VERSION_1_4值給VM,于是VM知道了其所使用的JNI版本了。此外,它也做了一些初期的動作(可呼叫任何本地函數(shù)),例如指令:
if (register_android_media_MediaPlayer(env) < 0) { LOGE("ERROR: MediaPlayer native registration failed\n"); goto bail; }
就將此組件提供的各個本地函數(shù)(Native Function)登記到VM里,以便能加快后續(xù)呼叫本地函數(shù)之效率。 JNI_OnUnload()函數(shù)與JNI_OnLoad()相對應(yīng)的。在加載C組件時會立即呼叫JNI_OnLoad()來進行組件內(nèi)的初期動作;而當(dāng)VM釋放該C組件時,則會呼叫JNI_OnUnload()函數(shù)來進行善后清除動作。當(dāng)VM呼叫JNI_OnLoad()或JNI_Unload()函數(shù)時,都會將VM的指標(Pointer)傳遞給它們,其參數(shù)如下:
jint JNI_OnLoad(JavaVM* vm, void* reserved) { ……… }
jint JNI_OnUnload(JavaVM* vm, void* reserved) { ……… }
在JNI_OnLoad()函數(shù)里,就透過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通常是多執(zhí)行緒(Multi-threading)的執(zhí)行環(huán)境。每一個執(zhí)行緒在呼叫JNI_OnLoad()時,所傳遞進來的JNIEnv指標值都是不同的。為了配合這種多執(zhí)行緒的環(huán)境,C組件開發(fā)者在撰寫本地函數(shù)時,可藉由JNIEnv指標值之不同而避免執(zhí)行緒的數(shù)據(jù)沖突問題,才能確保所寫的本地函數(shù)能安全地在Android的多執(zhí)行緒VM 里安全地執(zhí)行?;谶@個理由,當(dāng)在呼叫C組件的函數(shù)時,都會將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)函數(shù)時,就將env指標值傳遞過去。如此,在register_android_media_MediaPlayer()函數(shù)就能藉由該指標值而區(qū)別不同的執(zhí)行緒,以便化解數(shù)據(jù)沖突的問題。
例如,在register_android_media_MediaPlayer()函數(shù)里,可撰寫下述指令: if
((*env)->MonitorEnter(env, obj) != JNI_OK) { ……… }
查看是否已經(jīng)有其它執(zhí)行緒進入此對象,如果沒有,此執(zhí)行緒就進入該對象里執(zhí)行了。還有,也可撰寫下述指令:
if
((*env)->MonitorExit(env, obj) != JNI_OK) { ……… }
查看是否此執(zhí)行緒正在此對象內(nèi)執(zhí)行,如果是,此執(zhí)行緒就會立即離開。
|
|
來自: lifei_szdz > 《JNI》