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)用。

根據(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):

類型簽名用于使用字符串形式描述方法定義供jvm解析,其對(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