研究了一下Android JNI,有幾個(gè)知識(shí)點(diǎn)不太懂。
目錄
- Java線程與OS線程的區(qū)別與關(guān)聯(lián)
- JNI的作用
- JNIEnv和JavaVM是啥
- JNI中數(shù)據(jù)是如何傳遞的
Java線程與Native(OS)線程的區(qū)別
聯(lián)系:Java線程其實(shí)是一層OS線程的封裝,本質(zhì)上就是OS線程?!疽郧鞍姹镜腏ava線程不是OS線程,是JVM構(gòu)造的用戶態(tài)線程(Green Thread),不能充分利用CPU,后期已經(jīng)更改為使用OS線程實(shí)現(xiàn)。】【參考https://mp.weixin.qq.com/s/Gxqnf5vjyaI8eSYejm7zeQ】
區(qū)別:Java線程可以直接拿到JNIEnv,OS線程需要先attach到JVM,才可以拿到JNIEnv?!緜€(gè)人理解區(qū)別在于是否attach了JVM】
jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
static?jobject?g_class_loader?=?NULL;
static jmethodID g_find_class_method = NULL;
void?on_load()?{
????JNIEnv?*env?=?get_jni_env();
if (!env) {
return;
}
????jclass?capture_class?=?(*env)->FindClass(env,?"com/captureandroid/BMMCaptureEngine");
jclass class_class = (*env)->GetObjectClass(env, capture_class);
jclass class_loader_class = (*env)->FindClass(env, "java/lang/ClassLoader");
jmethodID class_loader_mid = (*env)->GetMethodID(env, class_class, "getClassLoader", "()Ljava/lang/ClassLoader;");
jobject local_class_loader = (*env)->CallObjectMethod(env, capture_class, class_loader_mid);
g_class_loader = (*env)->NewGlobalRef(env, local_class_loader);
g_find_class_method =
(*env)->GetMethodID(env, class_loader_class, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
}
jclass?find_class(const?char?*name)?{
JNIEnv *env = bmm_util_get_jni_env();
if (!env) {
return NULL;
}
jclass ret = (*env)->FindClass(env, name);
jthrowable exception = (*env)->ExceptionOccurred(env);
if (exception) {
(*env)->ExceptionClear(env);
jstring name_str = (*env)->NewStringUTF(env, name);
ret = (jclass)(*env)->CallObjectMethod(env, g_class_loader, g_find_class_method, name_str);
(*env)->DeleteLocalRef(env, name_str);
}
return ret;
}
JNI的作用
貼出別人翻譯的【官方文檔https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp16696】的一段話:
JNI最重要的設(shè)計(jì)目標(biāo)就是在不同操作系統(tǒng)上的JVM之間提供二進(jìn)制兼容,做到一個(gè)本地庫不需要重新編譯就可以運(yùn)行不同的系統(tǒng)的JVM上面。為了達(dá)到這一點(diǎn)兒,JNI設(shè)計(jì)時(shí)不能關(guān)心JVM的內(nèi)部實(shí)現(xiàn),因?yàn)镴VM的內(nèi)部實(shí)現(xiàn)機(jī)制在不斷地變,而我們必須保持JNI接口的穩(wěn)定。JNI的第二個(gè)設(shè)計(jì)目標(biāo)就是高效。我們可能會(huì)看到,有時(shí)為了滿足第一個(gè)目標(biāo),可能需要犧牲一點(diǎn)兒效率,因此,我們需要在平臺(tái)無關(guān)和效率之間做一些選擇。最后,JNI必須是一個(gè)完整的體系。它必須提供足夠多的JVM功能讓本地程序完成一些有用的任務(wù)。JNI不能只針對一款特定的JVM,而是要提供一系列標(biāo)準(zhǔn)的接口讓程序員可以把他們的本地代碼庫加載到不同的JVM中去。有時(shí),調(diào)用特定JVM下實(shí)現(xiàn)的接口可以提供效率,但更多的情況下,我們需要用更通用的接口來解決問題。
JNIEnv和JavaVM
就是個(gè)函數(shù)指針。
下圖是JNIEnv的指針結(jié)構(gòu):JNIEnv其實(shí)是一個(gè)指向本地線程數(shù)據(jù)的接口指針,指針里面包含指向函數(shù)接口的指針,每一個(gè)接口函數(shù)在這表中都有一個(gè)預(yù)定義的偏移位置,類似C 虛函數(shù)表。
代碼如下:
typedef const struct JNINativeInterface *JNIEnv;
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;
jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*,
jsize);
jclass (*FindClass)(JNIEnv*, const char*);
jobject (*AllocObject)(JNIEnv*, jclass);
jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list);
jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*);
...
};
JavaVM類似
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};
typedef const struct JNIInvokeInterface* JavaVM;
可將JNI命名空間與本地代碼分離,一個(gè)虛擬機(jī)可以提供多個(gè)版本的JNI函數(shù)表,用于不同場景。例如,虛擬機(jī)可支持兩種JNI函數(shù)表:
- 一個(gè)用于調(diào)試,做較多的錯(cuò)誤檢查。
- 一個(gè)用于發(fā)布,做較少的錯(cuò)誤檢查,更高效。
知識(shí)點(diǎn)2:JNIEnv是thread-local,只在當(dāng)前線程有效,Native方法不能將JNIenv從當(dāng)前線程傳遞到另一個(gè)線程。不能跨線程使用JNIEnv【至于JNIEnv為什么設(shè)計(jì)成thread-local,沒搞明白】。
知識(shí)點(diǎn)3:線程間雖然不共享JNIEnv,但是共享JavaVM,然后可以通過GetEnv獲取到當(dāng)前線程的JNIEnv。
jint GetEnv(JavaVM *vm, void **env, jint version);
知識(shí)點(diǎn)5:為什么在C語言中調(diào)用Native方法需要將JNIEnv當(dāng)作參數(shù)傳遞,而C 中卻不需要?
// C語言
jstring model_path = (*env)->NewStringUTF(env, path);
// C
jstring model_path = env->NewStringUTF(path);前面列出的JNIEnv是C語言形式,Java還單獨(dú)為C 封裝了一層JNIEnv,簡化版代碼:
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
{ return functions->GetVersion(this); }
jclass FindClass(const char* name)
{ return functions->FindClass(this, name); }
#endif
}其實(shí)本質(zhì)上還是調(diào)用的C語言那種形式的接口。
JNI中數(shù)據(jù)如何傳遞
這里不詳細(xì)介紹了,大體就是int,float這種基本類型采用拷貝,對象和byte數(shù)組等使用引用形式,所以其實(shí)Java層的byte字節(jié)流數(shù)據(jù)傳到Native層基本不耗時(shí),不會(huì)發(fā)生拷貝【但是Native層如果想使用持有這塊數(shù)據(jù),那就得自己拷貝一份了】。
還有些GlobalReference、LocalReference以及為什么要Delete LocalReference的這類知識(shí)點(diǎn),這些比較基礎(chǔ),就不介紹了,估計(jì)大家也都懂。