深入理解android卷一之jni小結(jié)

1.jni簡(jiǎn)介

native c/c++代碼是和平臺(tái)相關(guān)聯(lián)的,是可以直接運(yùn)行的,在java出現(xiàn)以前很多軟件由native語(yǔ)言編寫,包括Jvm也是由native語(yǔ)言編寫的,java提供了一套jni技術(shù)使得native和java相互調(diào)用。

Jni功能圖示

根據(jù)oracle文檔,如下情況需要使用jni:

  • The standard Java class library does not support the platform-dependent features needed by the application.
  • You already have a library written in another language, and wish to make it accessible to Java code through the JNI.
  • You want to implement a small portion of time-critical code in a lower-level language such as assembly.

使用jni可以:

  • Create, inspect, and update Java objects (including arrays and strings).
  • Call Java methods.
  • Catch and throw exceptions.
  • Load classes and obtain class information.
  • Perform runtime type checking.

使用jni的Invoication API可以把jvm嵌入native application中,而無(wú)需接觸jvm的源碼。

2.native函數(shù)和java函數(shù)關(guān)聯(lián)映射

jni java部分,MediaScanner.java:

public class MediaScanner
{ //①加載對(duì)應(yīng)的JNI庫(kù),media_jni是JNI庫(kù)的名字。實(shí)際加載動(dòng)態(tài)庫(kù)的時(shí)候會(huì)拓展成libmedia_jni.so,在Windows平臺(tái)上將拓展為media_jni.dll。
static{
    System.loadLibrary("media_jni");
    native_init();//調(diào)用native_init函數(shù)
 }
//②聲明一個(gè)native函數(shù)。native為Java的關(guān)鍵字,表示它將由JNI層完成,java僅聲明函數(shù)即可。
private static native final void native_init();
private native void processFile(String path, String mimeType,MediaScannerClient client);
}

jni native部分,android_media_MediaScanner.cpp:

//①這個(gè)函數(shù)是native_init的JNI層實(shí)現(xiàn)。
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
jclass clazz;
 clazz= env->FindClass("android/media/MediaScanner");
   fields.context = env->GetFieldID(clazz, "mNativeContext","I");
   return;
}
//這個(gè)函數(shù)是processFile的JNI層實(shí)現(xiàn)。
static void android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz, 
jstring path, jstring mimeType, jobject client)
{
    MediaScanner*mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
    constchar *pathStr = env->GetStringUTFChars(path, NULL);
    if(mimeType) {
    env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
}
}

java和native的映射注冊(cè):

靜態(tài)注冊(cè):

當(dāng)?shù)谝淮问褂玫侥骋粋€(gè)native函數(shù)的時(shí)候jvm會(huì)找對(duì)應(yīng)的native函數(shù),如果沒(méi)有找到native函數(shù),就會(huì)報(bào)錯(cuò);如果找到了則建立兩者映射關(guān)系(一個(gè)結(jié)構(gòu)體,分別存儲(chǔ)了java方法名和native方法名),以后再調(diào)用這個(gè)native函數(shù)可以直接使用映射關(guān)系直接找到了。 初次調(diào)用native函數(shù)時(shí)要根據(jù)函數(shù)名字搜索對(duì)應(yīng)的JNI層函數(shù)來(lái)建立關(guān)聯(lián)關(guān)系,這樣會(huì)影響第一次調(diào)用的運(yùn)行效率。動(dòng)態(tài)注冊(cè)可以解決這個(gè)問(wèn)題。

動(dòng)態(tài)注冊(cè):

SystemloadLibrary完成之后會(huì)嘗試回調(diào)對(duì)應(yīng)加載庫(kù)中的jint JNI_OnLoad(JavaVM* vm, void* reserved)方法,native庫(kù)可以選擇性的實(shí)現(xiàn)該方法,在該方法中自行向jvm注冊(cè)native函數(shù)和java函數(shù)的映射關(guān)系。如此便避免了靜態(tài)注冊(cè)的第一次使用還需要查找的問(wèn)題,這稱之為動(dòng)態(tài)注冊(cè)。

android的libmedia_jni.so的動(dòng)態(tài)注冊(cè)在android_media_MediaPlayer.cpp中實(shí)現(xiàn)。

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
   //該函數(shù)的第一個(gè)參數(shù)類型為JavaVM,這可是虛擬機(jī)在JNI層的代表喔,每個(gè)Java進(jìn)程只有一個(gè)
   JNIEnv* env = NULL;
    jintresult = -1;
    if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    goto bail;
    }
    //動(dòng)態(tài)注冊(cè)MediaScanner的JNI函數(shù)。
    if(register_android_media_MediaScanner(env) < 0) {
    goto bail;
}
returnJNI_VERSION_1_4;//必須返回大于1.1的值,否則會(huì)報(bào)錯(cuò)。
}

值得注意的是該方法默認(rèn)返回JNI_VERSION_1_1,而這個(gè)方法是在1_2中被添加的,需要返回1.2及以上版本才可以使用新的方法和特性。

The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary). JNI_OnLoad must return the JNI version needed by the native library.
In order to use any of the new JNI functions, a native library must export a JNI_OnLoad function that returns JNI_VERSION_1_2. If the native library does not export a JNI_OnLoad function, the VM assumes that the library only requires JNI version JNI_VERSION_1_1. If the VM does not recognize the version number returned by JNI_OnLoad, the native library cannot be loaded.

引用自oracle文檔網(wǎng)站:

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/jni-12.html#JNI_OnLoad

3.java和jni類型對(duì)應(yīng)及類型簽名對(duì)應(yīng)

原始類型:

原始類型對(duì)應(yīng)

引用類型對(duì)應(yīng):

引用類型對(duì)應(yīng)

類型簽名用于使用字符串形式描述方法定義供jvm解析,其對(duì)應(yīng)列表如下:

類型簽名對(duì)應(yīng)

4.native函數(shù)運(yùn)行時(shí)參數(shù)和注意事項(xiàng)

  • jni調(diào)用可以理解為同步調(diào)用,JniEnv是基于特定線程的。代表函數(shù)運(yùn)行時(shí)的上下文環(huán)境,jni提供的所有函數(shù)方法幾乎都需要通過(guò)它來(lái)使用,需要注意的是不同的線程具有不同的JniEnv,所以不能將其保存為全局引用以期望另外的線程可以訪問(wèn)。如果當(dāng)前線程還未具有JniEnv,需要先將其綁定到j(luò)vm來(lái)獲取對(duì)應(yīng)的JniEnv,運(yùn)行完需要解綁線程。
  • jni引用變量分為局部、全局、和全局弱引用。全局引用不會(huì)被java的gc回收,全局弱引用存在被回收的可能,局部引用當(dāng)方法結(jié)束后自動(dòng)被回收,方法參數(shù)的引用都是局部引用,方法中定義的引用都是局部引用,局部引用可以手動(dòng)立刻解除占用提高效率,如下:
for(inti = 0; i < 100; i++)
{
    jstring pathStr = mEnv->NewStringUTF(path);
    ......//做一些操作
    //mEnv->DeleteLocalRef(pathStr); //不立即釋放Local Reference會(huì)導(dǎo)致創(chuàng)建100個(gè)局部引用,直到方法結(jié)束才會(huì)釋放。

}
  • jni中的參數(shù)。會(huì)比java中的額外多兩個(gè)參數(shù),第一個(gè)參數(shù)一定是JniEnv引用,第二個(gè)參數(shù)根據(jù)是否是靜態(tài)方法有所不同。如果java世界聲明該方法是非靜態(tài)的,則為java對(duì)象引用,如果是靜態(tài)的則為對(duì)應(yīng)的java class引用。

  • jfieldID和jmethodID。對(duì)應(yīng)java世界的類字段和方法,jni提供方法可以在java的方法和jmethodID之間相互轉(zhuǎn)換,jfildD也提供了相關(guān)的轉(zhuǎn)換方法。

  • jni中的異常。因?yàn)榇蟛糠謈庫(kù)不確保運(yùn)行時(shí)參數(shù)沒(méi)有錯(cuò)誤,如果確??赡軙?huì)導(dǎo)致判斷兩次降低效率,所以參數(shù)無(wú)誤應(yīng)該有調(diào)用者來(lái)確保。jni遇見(jiàn)異常之后并不像Java一樣終止運(yùn)行,會(huì)記錄異常并繼續(xù)運(yùn)行,直到從JNI層返回到Java層后,虛擬機(jī)才會(huì)拋出這個(gè)異常。jni函數(shù)可以在合適的時(shí)刻執(zhí)行ExceptionCheck()來(lái)主動(dòng)檢測(cè)有無(wú)異常被記錄,并選擇return來(lái)終止函數(shù)的運(yùn)行。此外,jni可以主動(dòng)拋出任意java世界的異常。jni有兩種方式可以處理異常:a.使用ThrowNew拋出自定義異常并使用return返回,或者干脆直接return讓java代碼拋出對(duì)應(yīng)異常;b.使用ExceptionClear()清除異常,并自己處理掉異常。

Most C library functions do not guard against programming errors. The printf() function, for example, usually causes a runtime error, rather than returning an error code, when it receives an invalid address. Forcing C library functions to check for all possible error conditions would likely result in such checks to be duplicated--once in the user code, and then again in the library.

The programmer must not pass illegal pointers or arguments of the wrong type to JNI functions

參考鏈接:

https://docs.oracle.com/en/java/javase/11/docs/specs/jni/index.html

http://wiki.jikexueyuan.com/project/deep-android-v1/jni.html

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

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