java 在已有的so基础上封装jni_[干货]再见,Android JNI 封装
1 前言
2 JNI 速查表
2.1 Java 和 Native 數據類型映射表
2.2 引用類型
3 JNI 理論基礎速覽
4 JNI 常用場景示例
4.1 字符串傳遞(java->native)
4.2 字符串返回(native->java)
4.3 數組傳遞(java->native)
4.4 其他復雜對象傳遞(java->native)
4.5 復雜對象返回(native->java)
4.6 復雜數組對象返回(native->java)
4.7 指針對象處理(nativejava)
4.8 超級復雜對象操作(nativejava)
4.9 靜態成員方法訪問(native->java)
4.10 Context 訪問
4.11 異常處理
4.12 關于緩存
5 JNI 庫一鍵構建框架
6 封裝思路和開發工具
7 后續高級擴展
8 小結
9 參考資料
1 前言
最近名名接到友鄰團隊的“求助”,臨時調度幫助其 SDK 封裝 JNI SDK,下面就用 SDK 和 JNI SDK 來區分這兩個 SDK。以前我也不是搞這個的,但是因為干過一兩次,多少有點經驗,之前第一次封裝后,我覺得這玩意可以總結成通用模板,但是因為本身對它不感興趣,也就沒去弄了,今天又來一次,我覺得有必要了,因為它就是個體力活。今天總結這個模板以及封裝思路,可以讓我們快速的實現 JNI 封裝。有如下這么些數據:
- 最后分解得到基礎數據類,包括枚舉體和通用數據體類總計:40個
- 涉及 API 個數:20個
- 涉及 API 復雜度:
- 有對象數組操作
- 有指針操作
- 最深三層類的嵌套
- 二層嵌套和三層嵌套的類占據 1/3 左右
- 涉及 Assets 資源文件操作
我自己挑戰了一下,花了兩天,按一天 8 小時工作量算(不包括吃飯、午睡),完成了:
- 依賴 SDK 的熟悉,畢竟需要了解流程,在 Java 層對接口形式做適度的優化
- Git 項目同步管理(很規矩的那種)
- SDK 數據結構分解到 Java 類
- 設計 Java APIs
- 編譯框架(以前有 Native Headers 生成模板)
- 實現 YAML 解析,我特意去找了個開源庫,并對它實現了 Bazel 工程編譯,使得 JNI SDK 直接依賴 Github 源碼(爽歪歪)
- 去除注釋,C++ 和 Java 代碼行數 2924 行 :
- $ find . -name "*.*" | xargs cat | grep -v -e ^$ -e ^\s*\/\/.*$ | wc -l
說了這么多,總之就是用了我的方法,兩天內輕松完成了一個 JNI SDK 的封裝。這次總結完成后,估計封裝效率又會提升一截(寫這文章前,我剛好弄完封裝任務,剩下的時間就來總結了,希望同事看到了不要告狀,不然你就看不到這篇嘔心瀝血的文章分享了)。
不來虛的,直奔重點。對了,JNI 基礎你需不需要呢?我覺得吧,看完這個,你都不用去了解 JNI 是個啥了,囫圇吞棗,依樣畫葫蘆,直接照著干,ctrl-c\ctrl-v,一梭到底(開玩笑開玩笑,多少還是要有點概念,至于這些就需要你自己去其他地方去了解了,包括 JNI 是啥、為啥要有 JNI ?、有啥利弊?、JNI Native 函數加載執行流程、JNI 靜態/動態注冊、JNI 引用、C/C++ 內存模型、Java 內存模型、JVM 內存結構、JVM GC 如何工作的等,額...)。
2 JNI 速查表
2.1 Java 和 Native 數據類型映射表
| boolean | jboolean / uint8_t | unsigned 8 bits | Z |
| byte | jbyte / int8_t | signed 8 bits | B |
| char | jchar / uint16_t | unsigned 16 bits | C |
| short | jshort / int16_t | signed 16 bits | S |
| int | jint / int32_t | signed 32 bits | I |
| long | jlong / int64_t | signed 64 bits | J |
| float | jfloat / float | 32 bits | F |
| double | jdouble / double | 64 bits | D |
| void | void | N/A | V |
| Object | jobject | 引用對象大小,包括 jclass/jstring/jarray/jthrowable | Lfully/qualified/class/name; |
| String | jstring / c++對象類 | N/A | Ljava/lang/String; |
| Object[] | jobjectArray | N/A | N/A |
| boolean[] | jbooleanArray | N/A | [Z |
| byte[] | jbyteArray | N/A | [B |
| char[] | jcharArray | N/A | [C |
| short[] | jshortArray | N/A | [S |
| int[] | jintArray | N/A | [I |
| long[] | jlongArray | N/A | [J |
| float[] | jfloatArray | N/A | [F |
| double[] | jdoubleArray | N/A | [D |
| 函數 | N/A | public native long f (int n, String s, int[] arr); | (argument-types)return-type,比如(ILjava/lang/String;[I)J |
上面的東西你不知道,還有一個辦法,就是去自動生成的 JNI 頭文件里可以得知,想要啥,自己寫個測試函數,然后生成一下就可以知道了。
2.2 引用類型
- jobject (all Java objects)
- jobjectArray (object arrays)
- jbooleanArray (boolean arrays)
- jbyteArray (byte arrays)
- jcharArray (char arrays)
- jshortArray (short arrays)
- jintArray (int arrays)
- jlongArray (long arrays)
- jfloatArray (float arrays)
- jdoubleArray (double arrays)
- jclass (java.lang.Class objects)
- jstring (java.lang.String objects)
- jarray (arrays)
- jthrowable (java.lang.Throwable objects)
想更清楚的了解的朋友,可以去 jni.h 和 jni_md.h 查看。
3 JNI 理論基礎速覽
- 「關于對象回收」:通俗點,對象只有一個,即在 Java 層 new 了,就不用在 Native 層再去 new;反之,要在 Native 層返回一個對象,則需要創建;Java 層內存是 JVM 自動管理的,Native 層,C/C++ 編寫,你懂的。
- 「關于引用」:
- NewLocalRef:返回局部引用
- FindClass/GetObjectClass:返回局部引用(這兩個函數作用一樣,只是傳入參數不一樣)。
- NewObject:如果返回 Java 層繼續引用,則局部引用不會被釋放,如果是通過參數傳遞,賦值給參數,函數調用完畢就會釋放。
- GetObjectClass:返回局部引用
- NewCharArray:返回局部應用
- ......
- 傳遞給 Native 方法的每個參數,以及 JNI 函數返回的幾乎每個對象都屬于局部引用,包括 jobject 及其所有子類。
- 局部引用僅在創建它們的線程中有效,不得將局部引用從一個線程傳遞到另一個線程。
- jfieldID 和 jmethodID 屬于不透明類型,不是對象引用,因此總是可以緩存他們,以提升效率。而對于 jclass 就需要注意了,得使用全局引用。
- 基本數據類型,如 int、char 之類的,在 Java 和 Native 層之間是直接拷貝一份,這個跟我們接觸的傳值、傳引用是一樣的。任何的 Java 對象都是通過引用傳遞的。
- 「局部引用」(Local Reference): 在函數返回后會被 JVM 自動釋放掉,或者調用 (*env)->DeleteLocalRef(env, local_ref) 手動釋放(「不管怎樣」,盡量手動釋放,防止局部引用表溢出,Android 8.0 上支持無限制的局部引用)
- 「全局引用」(Global Reference): 調用 NewGlobalRef,JVM 不會自動釋放,基于局部引用創建,可跨方法、線程使用;必須調用 (*env)->DeleteGlobalRef(env, g_ref); 手動釋放。
- 「弱全局引用」(Weak Global Reference): 調用 NewWeakGlobalRef 基于局部引用或全局引用創建,可跨方法、線程使用;在 JVM 認為應該回收它的時候進行回收釋放,或調用 (*env)->DeleteWeakGlobalRef(env, g_ref) 手動釋放;不同上面兩種引用,不會阻止 GC 回收它引用的對象;
- 引用比較:(*env)->IsSameObject(env, obj1_ref, obj2_ref),判斷引用對象(不分局部、全局、弱全局)是否相同。
- 「關于類」:我們都知道類有構造函數、實例、成員方法、成員變量。
- 「關于性能」:Native 層查找方法 ID、字段 ID、Class 引用效率是較低的(JVM 原因),因此可以基于這點在 Native 層做緩存優化。
- FindClass()
- GetFieldID()
- GetMethodId()
- GetStaticMethodID()
- GetIntField()
- 「關于緩存」:
- JavaVM* vm 在整個進程中唯一
- 采用全局變量的方式緩存
- 靜態局部變量緩存,直到程序結束才會釋放;不加鎖,多線程,存在多次緩存情況。
- 對局部引用進行靜態變量緩存,會存在引用內容釋放,成為野指針風險
- 全局變量緩存,聲明定義 public static native 方法,到 static {} 中調用,然后到 Native 層實現靜態方法初始化相關全局變量,也可以實現緩存
- 返回基本類型的 Native 函數,不能造成全局引用、弱全局引用、局部引用的積累,即記得手動釋放,防止造成內存溢出
- 返回引用類型的 Native 函數,除了要返回的引用之外,也不能造成任何的全局引用、弱全局引用、局部引用的積累
- 對于 jmethodID 和 jfieldID 的緩存,是線程安全的。
- jclass 需要結合 NewGlobalRef 全局引用來實現緩存。
- jint JNI_OnLoad(JavaVM* vm, void* reserved){} 在 System.loadLibary 加載本機代碼后會自動調用;void JNI_OnUnload(JavaVM *vm, void *reserved){} 當 Classloader 銷毀后會自動調用。
4 JNI 常用場景示例
4.1 字符串傳遞(java->native)
//?public?native?CommonStatus?SetString(String?str);JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_SetString(JNIEnv?*env,?jobject,?jstring?j_str)?{
????const?char?*c_str?=?NULL;
????c_str?=?env->GetStringUTFChars(j_str,?NULL);
????if?(NULL?==?c_str)?{
????????TEST_LOG_E("Failed?to?get?string?UTF?chars.");
????????return?getStatus(env,?FAILED);
????}
????TEST_LOG_D("c?str:?%s",?c_str);
????//?如使用?GetStringUTFRegion?與?GetStringRegion,則內部未分配內存,無需釋放
????env->ReleaseStringUTFChars(j_str,?c_str);
????return?getStatus(env,?SUCCESS);
}
4.2 字符串返回(native->java)
//?public?native?String?GetString();JNIEXPORT?jstring?JNICALL?Java_net_xiaobaiai_test_APIs_GetString(JNIEnv?*env,?jobject)?{
????char?str[60]?=?"Hello";
????//?1.?可以用?const?char?*
????//const?char?*str?=?"Hello";
????//?2.?可以用?std::string?str?=?std::string("Hello");?str.c_str()
????jstring?result;
????result?=?env->NewStringUTF(str);
????return?result;
}
4.3 數組傳遞(java->native)
4.3.1 基本類型數組
//?public?native?CommonStatus?SetBaseTypeArray(int[]?intArray);JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_SetBaseTypeArray(JNIEnv?*env,?jobject,?jintArray?j_array)?{
????//?step:?get?length
????int?arr_len?=?env->GetArrayLength(j_array);
????//?step:?get?array
????int?*?array?=?env->GetIntArrayElements(j_array,?NULL);
????if?(!array)?{
????????TEST_LOG_E("Failed?to?get?int?array?elements");
????????return?getStatus(env,?FAILED);
????}
????for?(int?i?=?0;?i?????????TEST_LOG_D("int?array[%d]?=?%d",?i,?array[i]);
????}
????//?也可以使用?GetIntArrayRegion/GetPrimitiveArrayCritical?區別不在展開
????env->ReleaseIntArrayElements(j_array,?array,?0);
????return?getStatus(env,?SUCCESS);
}
4.3.2 對象類型數組
//?public?native?CommonStatus?SetStringArray(String[]?strArray);JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_SetStringArray(JNIEnv?*env,?jobject,?jobjectArray?j_str_array)?{
????//?step1:?get?array?length
????int?array_size?=?env->GetArrayLength(j_str_array);
????//?step2:?get?object?array?item?with?a?loop
????for?(int?i?=?0;?i?????????jstring?j_str?=?(jstring)(env->GetObjectArrayElement(j_str_array,?i));
????????const?char?*c_str?=?env->GetStringUTFChars(j_str,?NULL);
????????TEST_LOG_D("str?array[%d]?=?%s",?i,?c_str);
????????env->ReleaseStringUTFChars(j_str,?c_str);
????}
????return?getStatus(env,?SUCCESS);
}
4.4 其他復雜對象傳遞(java->native)
//?public?native?CommonStatus?SetPoint2DArray(Point2D[]?pointArray);JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_SetPoint2DArray(JNIEnv?*env,?jobject,?jobjectArray?j_array)?{
????//?step1:?get?array?length
????int?array_len?=?env->GetArrayLength(j_array);
????//?step2:?get?object?array?item?with?a?loop
????for?(int?i?=?0;?i?????????//?step2.1:?get?array?element
????????jobject?j_object?=?env->GetObjectArrayElement(j_array,?i);
????????if?(!j_object)?{
????????????TEST_LOG_E("Failed?to?get?object?array?element");
????????????return?getStatus(env,?FAILED);
????????}
????????//?step2.2:?get?value
????????float?x?=?env->GetFloatField(j_object,?point2d.x);
????????float?y?=?env->GetFloatField(j_object,?point2d.y);
????????TEST_LOG_D("array[%d],?x?=?%f,?y?=?%f",?i,?x,?y);
????}
????return?getStatus(env,?SUCCESS);
}
//?public?native?CommonStatus?SetPoint(PointF?point);
JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_SetPoint(JNIEnv?*env,?jobject,?jobject?j_pointf)?{
?????//?step2.2:?get?value
????float?x?=?env->GetFloatField(j_pointf,?graphics_pointf.x);
????float?y?=?env->GetFloatField(j_pointf,?graphics_pointf.y);
????TEST_LOG_E("x?=?%f,?y?=?%f",?x,?y);
????return?getStatus(env,?SUCCESS);
}
//?public?native?CommonStatus?SetPointArrayList(ArrayList?array);
JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_SetPointArrayList(JNIEnv?*env,?jobject,?jobject?j_point_array)?{
????int?point_count?=?static_cast<int>(env->CallIntMethod(j_point_array,?array_list.size));
????if?(point_count?1)?{
????????TEST_LOG_W("The?array?size?less?than?1");
????????return?getStatus(env,?FAILED);
????}
????double?x,?y;
????for?(int?i?=?0;?i?????????jobject?point?=?env->CallObjectMethod(j_point_array,?array_list.get,?i);
????????jfloat?x?=?env->GetFloatField(point,?graphics_pointf.x);
????????jfloat?y?=?env->GetFloatField(point,?graphics_pointf.y);
????????env->DeleteLocalRef(point);
????????TEST_LOG_D("x:?%lf,?y:?%lf",?x,?y);
????}
????return?getStatus(env,?SUCCESS);
}
4.5 復雜對象返回(native->java)
//?public?native?String[]?GetStringArray(int?size);JNIEXPORT?jobjectArray?JNICALL?Java_net_xiaobaiai_test_APIs_GetStringArray(JNIEnv?*env,?jobject,?jint?j_size)?{
????jobjectArray?result;
????result?=?(jobjectArray)env->NewObjectArray(j_size,?env->FindClass("java/lang/String"),?env->NewStringUTF(""));
????if?(!result)?{
????????TEST_LOG_E("Failed?to?new?object?array");
????????return?NULL;
????}
????for(int?i?=?0;?i??????????env->SetObjectArrayElement(result,?i,
????????????env->NewStringUTF((std::string("item?")?+?std::to_string(i)).c_str()));
????}
????return?result;
}
//?public?native?PointF?GetPointf();
JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_GetPointf(JNIEnv?*env,?jobject?j_obj)?{
????//?The?generated?values?are?for?testing?only
????jobject?pt_object?=?env->NewObject(graphics_pointf.clz,
????????graphics_pointf.constructor,?j_obj,?1.22f,?3.14f);
????return?pt_object;
}
4.6 復雜數組對象返回(native->java)
4.6.1 基本類型二維數組
//?public?native?int[][]?GetInt2DArray(int?row,?int?col);JNIEXPORT?jobjectArray?JNICALL?Java_net_xiaobaiai_test_APIs_GetInt2DArray(JNIEnv?*env,?jobject,?jint?row,?jint?col)?{
????jobjectArray?result;
????jclass?cls_int_array;
????jint?i,j;
????//?step1:?find?class
????cls_int_array?=?env->FindClass("[I");
????if?(cls_int_array?==?NULL)?{
????????return?NULL;
????}
????//?step2:?create?int?array?object
????result?=?env->NewObjectArray(row,?cls_int_array,?NULL);
????if?(result?==?NULL)?{
????????return?NULL;
????}
????//?step3:?set?value
????for?(i?=?0;?i?????????jint?buff[256];
????????jintArray?int_array?=?env->NewIntArray(col);
????????if?(int_array?==?NULL)?{
????????????return?NULL;
????????}
????????for?(j?=?0;?j?????????????buff[j]?=?i?+?j;
????????}
????????env->SetIntArrayRegion(int_array,?0,?col,?buff);
????????env->SetObjectArrayElement(result,?i,?int_array);
????????env->DeleteLocalRef(int_array);
????}
????return?result;
}
4.6.2 復雜對象數組
//?public?native?ArrayList?GetPointArrayList();JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_GetPointArrayList(JNIEnv?*env,?jobject?j_obj)?{
????const?int?array_size?=?5;
????jobject?result?=?env->NewObject(array_list.clz,?array_list.constructor,?array_size);
????for?(int?i?=?0;?i?????????//?step?1/2:?new?point
????????//?The?generated?values?are?for?testing?only
????????jobject?pt_object?=?env->NewObject(graphics_pointf.clz,?graphics_pointf.constructor,?j_obj,?0?+?i,?1?+?i);
????????//?step?2/2:?add?point?to?array?list
????????env->CallBooleanMethod(result,?array_list.add,?pt_object);
????????env->DeleteLocalRef(pt_object);
????}
????return?result;
}
4.7 指針對象處理(nativejava)
//?public?native?CommonStatus?InitHandle(PointHandle?handle);JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_InitHandle(JNIEnv?*env,?jobject,?jobject?j_handle)?{
????CHandle*?handle?=?(CHandle*)malloc(sizeof(CHandle));
????if?(!handle)?{
????????TEST_LOG_E("Failed?to?new?handle.");
????????return?getStatus(env,?FAILED);
????}
????CommonStatus?status?=?InitCHandle(handle);
????if?(SUCCESS?!=?status)?{
????????TEST_LOG_E("Failed?to?init?handle?with?%d.",?status);
????????return?getStatus(env,?status);
????}
????jclass?clz_handle?=?env->GetObjectClass(j_handle);
????if?(NULL?==?clz_handle)?{
????????TEST_LOG_E("Failed?to?get?handle?object?class.");
????????return?getStatus(env,?FAILED);
????}
????jfieldID?p_handle?=?env->GetFieldID(clz_handle,?"p_handle",?"J");
????if?(NULL?==?p_handle)?{
????????TEST_LOG_E("Failed?to?get?handle?pointer.");
????????return?getStatus(env,?FAILED);
????}
????TEST_LOG_E("handle?value:?%ld",?(jlong)handle);
????env->SetLongField(j_handle,?p_handle,?(jlong)handle);
????return?getStatus(env,?SUCCESS);
}
//?public?native?CommonStatus?DestroyHandle(PointHandle?handle);
JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_DestroyHandle(JNIEnv?*env,?jobject,?jobject?j_handle)?{
????jlong?handle?=?getHandle(env,?j_handle);
????CHandle?*p_handle?=?(CHandle*)handle;
????if?(!p_handle)?{
????????TEST_LOG_E("Failed?to?get?handle.");
????????return?getStatus(env,?FAILED);
????}
????CommonStatus?status?=?DestroyCHandle(*p_handle);
????if?(SUCCESS?!=?status)?{
????????TEST_LOG_E("Failed?to?destroy?handle?with?%d.",?status);
????????return?getStatus(env,?FAILED);
????}
????free(p_handle);
????p_handle?=?NULL;
????return?getStatus(env,?SUCCESS);
}
4.8 超級復雜對象操作(nativejava)
//?public?native?CStruct?GetCStruct();JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_GetCStruct(JNIEnv?*env,?jobject)?{
????//?Note:?The?check?of?parameter?boundary?and?function?return?value?is?omitted?here!!!
????//?Create?Point2D
????jobject?j_point2d?=?env->NewObject(point2d.clz,?point2d.constructor);
????env->SetFloatField(j_point2d,?point2d.x,?1.1f);
????env->SetFloatField(j_point2d,?point2d.y,?1.2f);
????TEST_LOG_D("Create?Point2d?Successfully");
????//?Create?Rect
????jobject?j_rect?=?env->NewObject(rect.clz,?rect.constructor);
????env->SetIntField(j_rect,?rect.left,?1);
????env->SetIntField(j_rect,?rect.top,?2);
????env->SetIntField(j_rect,?rect.right,?3);
????env->SetIntField(j_rect,?rect.bottom,?4);
????TEST_LOG_D("Create?Rect?Successfully");
????//?Create?MyRect:?Reuse?the?point?that?have?been?created
????jobject?j_my_rect?=?env->NewObject(my_rect.clz,?my_rect.constructor);
????env->SetObjectField(j_my_rect,?my_rect.left_top,?j_point2d);
????env->SetObjectField(j_my_rect,?my_rect.right_bottom,?j_point2d);
????TEST_LOG_D("Create?MyRect?Successfully");
????//?Create?Inner?Enum
????jobject?j_inner_enum_one_obj?=?env->GetStaticObjectField(cstruct_cache_header.inner_enum_header.clz,
????????cstruct_cache_header.inner_enum_header.jid_one);
????//?Create?Inner?Class
????jobject?j_inner_class_obj?=?env->NewObject(cstruct_cache_header.innter_class_header.clz,?cstruct_cache_header.innter_class_header.constructor);
????char?c_msg[60]?=?"Hello";
????jstring?j_msg?=?env->NewStringUTF(c_msg);
????env->SetObjectField(j_inner_class_obj,?cstruct_cache_header.innter_class_header.msg,?j_msg);
????TEST_LOG_D("Create?Inner?Class?Successfully");
????//?Create?byte[]
????const?int?c_data_len?=?256;
????jbyte?c_data[c_data_len]?=?{'b',?'i',?'a',?'d',?'a',?'m',?'m'};
????jbyteArray?j_data?=?env->NewByteArray(c_data_len);
????env->SetByteArrayRegion(j_data,?0,?c_data_len,?c_data);
????TEST_LOG_D("Create?Byte?Array?Successfully");
????//?Create?2d?array
????const?int?c_double_d_array_row?=?10;
????const?int?c_double_d_array_col?=?5;
????jclass?double_d_clz?=?env->FindClass("[I");
????jobjectArray?j_double_d_array?=?env->NewObjectArray(c_double_d_array_row,?double_d_clz,?NULL);
????for?(int?i?=?0;?i?????????jintArray?j_int_array?=?env->NewIntArray(c_double_d_array_col);
????????int?c_int_array_data[c_double_d_array_col]?=?{1,?2,?3,?4,?5};
????????env->SetIntArrayRegion(j_int_array,?0,?c_double_d_array_col,?c_int_array_data);
????????env->SetObjectArrayElement(j_double_d_array,?i,?j_int_array);
????}
????TEST_LOG_D("Create?2d?Array?Successfully");
????//?Create?CStruct:?If?you?created?an?object?externally(Java?Layer),?you?don't?need?to?create?it?here.
????jobject?j_struct?=?env->NewObject(cstruct_cache_header.clz,?cstruct_cache_header.constructor);
????TEST_LOG_D("Create?CStruct?Successfully");
????//?Set?values
????env->SetObjectField(j_struct,?cstruct_cache_header.jid_point2d,?j_point2d);
????env->SetBooleanField(j_struct,?cstruct_cache_header.jid_ztype,?JNI_FALSE);
????env->SetCharField(j_struct,?cstruct_cache_header.jid_ctype,?'Y');
????env->SetShortField(j_struct,?cstruct_cache_header.jid_stype,?8);
????env->SetIntField(j_struct,?cstruct_cache_header.jid_itype,?9);
????env->SetLongField(j_struct,?cstruct_cache_header.jid_jtype,?10);
????env->SetFloatField(j_struct,?cstruct_cache_header.jid_ftype,?11.0f);
????env->SetDoubleField(j_struct,?cstruct_cache_header.jid_dtype,?12.0);
????env->SetObjectField(j_struct,?cstruct_cache_header.jid_data,?j_data);
????env->SetObjectField(j_struct,?cstruct_cache_header.jid_inner_enum,?j_inner_enum_one_obj);
????env->SetObjectField(j_struct,?cstruct_cache_header.jid_innter_class,?j_inner_class_obj);
????env->SetObjectField(j_struct,?cstruct_cache_header.jid_rect,?j_rect);
????env->SetObjectField(j_struct,?cstruct_cache_header.jid_myrect,?j_my_rect);
????env->SetObjectField(j_struct,?cstruct_cache_header.jid_double_d_array,?j_double_d_array);
????return?j_struct;
}
//?public?native?CommonStatus?SetCStruct(CStruct?data);
JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_SetCStruct(JNIEnv?*env,?jobject,?jobject?j_struct)?{
????if?(!j_struct)?{
????????TEST_LOG_E("Input?struct?data?is?null.");
????????return?getStatus(env,?FAILED);
????}
????//?Note:?The?check?of?parameter?boundary?and?function?return?value?is?omitted?here!!!
????//?Get?byte[]?data
????jbyteArray?j_data_array?=?(jbyteArray)env->GetObjectField(j_struct,?cstruct_cache_header.jid_data);
????if?(NULL?==?j_data_array)?{
????????TEST_LOG_E("Failed?to?get?object?field.");
????????return?getStatus(env,?FAILED);
????}
????jbyte?*c_data?=?env->GetByteArrayElements(j_data_array,?JNI_FALSE);
????//?Get?basic?type?value
????jboolean?c_ztype?=?env->GetBooleanField(j_struct,?cstruct_cache_header.jid_ztype);
????jchar?c_ctype?=?env->GetCharField(j_struct,?cstruct_cache_header.jid_ctype);
????jshort?c_stype?=?env->GetShortField(j_struct,?cstruct_cache_header.jid_stype);
????jint?c_itype?=?env->GetIntField(j_struct,?cstruct_cache_header.jid_itype);
????jlong?c_jtype?=?env->GetLongField(j_struct,?cstruct_cache_header.jid_jtype);
????jfloat?c_ftype?=?env->GetFloatField(j_struct,?cstruct_cache_header.jid_ftype);
????jdouble?c_dtype?=?env->GetDoubleField(j_struct,?cstruct_cache_header.jid_dtype);
????TEST_LOG_E("Get?basic?type?value?successfully");
????//?Get?Point2D?value
????jobject?j_point2d?=?env->GetObjectField(j_struct,?cstruct_cache_header.jid_point2d);
????jfloat?c_point2d_x?=?env->GetFloatField(j_point2d,?point2d.x);
????jfloat?c_point2d_y?=?env->GetFloatField(j_point2d,?point2d.y);
????TEST_LOG_E("Get?Point2D?value?successfully");
????//?Get?Rect?value
????jobject?j_rect?=?env->GetObjectField(j_struct,?cstruct_cache_header.jid_rect);
????jint?c_rect_left?=?env->GetIntField(j_rect,?rect.left);
????jint?c_rect_top?=?env->GetIntField(j_rect,?rect.top);
????jint?c_rect_right?=?env->GetIntField(j_rect,?rect.right);
????jint?c_rect_bottom?=?env->GetIntField(j_rect,?rect.bottom);
????TEST_LOG_E("Get?Rect?value?successfully");
????//?Get?MyRect?value
????jobject?j_my_rect?=?env->GetObjectField(j_struct,?cstruct_cache_header.jid_myrect);
????jobject?j_my_rect_point2d_lefttop?=?env->GetObjectField(j_my_rect,?my_rect.left_top);
????jobject?j_my_rect_point2d_rightbottom?=?env->GetObjectField(j_my_rect,?my_rect.right_bottom);
????jfloat?c_my_rect_lefttop_x?=?env->GetFloatField(j_my_rect_point2d_lefttop,?point2d.x);
????jfloat?c_my_rect_lefttop_y?=?env->GetFloatField(j_my_rect_point2d_rightbottom,?point2d.y);
????jfloat?c_my_rect_rightbottom_x?=?env->GetFloatField(j_my_rect_point2d_rightbottom,?point2d.x);
????jfloat?c_my_rect_rightbottom_y?=?env->GetFloatField(j_my_rect_point2d_rightbottom,?point2d.y);
????TEST_LOG_E("Get?MyRect?value?successfully");
????//?Get?inner?enum?type
????jint?img_format_value?=?env->CallIntMethod(j_struct,?cstruct_cache_header.inner_enum_md);
????TEST_LOG_E("Get?Inner?enum?value?successfully");
????//?Get?inner?class?type
????jobject?j_inner_class?=?env->GetObjectField(j_struct,?cstruct_cache_header.jid_innter_class);
????jstring?j_inner_class_msg?=?(jstring)env->GetObjectField(j_inner_class,?cstruct_cache_header.innter_class_header.msg);
????const?char?*c_str?=?env->GetStringUTFChars(j_inner_class_msg,?NULL);
????if?(NULL?==?c_str)?{
????????TEST_LOG_E("Failed?to?get?string?UTF?chars.");
????????return?getStatus(env,?FAILED);
????}
????TEST_LOG_D("c?str:?%s",?c_str);
????//?Release?byte[]
????env->ReleaseByteArrayElements(j_data_array,?c_data,?0);
????return?getStatus(env,?SUCCESS);
}
4.9 靜態成員方法訪問(native->java)
//?public?native?void?CallStaticMethod();JNIEXPORT?void?JNICALL?Java_net_xiaobaiai_test_APIs_CallStaticMethod(JNIEnv?*env,?jobject)?{
????jstring?j_str?=?env->NewStringUTF("Hello?Static?Method");
????env->CallStaticVoidMethod(static_method_header.clz,?static_method_header.static_md,?j_str,?100);
????env->DeleteLocalRef(j_str);
}
4.10 Context 訪問
//?public?native?CommonStatus?SetContext(Context?context);JNIEXPORT?jobject?JNICALL?Java_net_xiaobaiai_test_APIs_SetContext(JNIEnv?*env,?jobject,?jobject?context)?{
????jclass?context_clz?=?env->GetObjectClass(context);
????//?get?android?application?package?name
????jmethodID?m_getpackagename_id?=?env->GetMethodID(context_clz,?"getPackageName",?"()Ljava/lang/String;");
????if?(!context_clz?||?!m_getpackagename_id)?{
????????TEST_LOG_E("Failed?to?get?class?or?method?id");
????????return?getStatus(env,?FAILED);
????}
????jstring?j_pkg_name?=?static_cast(env->CallObjectMethod(context,?m_getpackagename_id));if?(!j_pkg_name)?{
????????TEST_LOG_E("Failed?to?call?object?method.");return?getStatus(env,?FAILED);
????}const?char*?pkg_name?=?env->GetStringUTFChars(j_pkg_name,?0);if?(NULL?==?pkg_name)?{
????????TEST_LOG_E("Failed?to?get?string?UTF?chars.");return?getStatus(env,?FAILED);
????}
????TEST_LOG_D("package?name?=?%s",?pkg_name);
????env->ReleaseStringUTFChars(j_pkg_name,?pkg_name);return?getStatus(env,?SUCCESS);
}
4.11 異常處理
- env->ExceptionOccurred
- env->ExceptionClear
在 native 層處理異常,這里就不展開了。
4.12 關于緩存
通過 jint JNI_OnLoad(JavaVM* vm, void* reserved 實現緩存初始化,void JNI_OnUnload(JavaVM *vm, void *reserved 實現緩存釋放。緩存主要實現對 jclass、jfieldID、jmethodID 的緩存,具體可以參見:https://github.com/yicm/BazelMixedLanguage 代碼實現。
5 JNI 庫一鍵構建框架
- 支持 JNI Native 頭文件自動生成
- 支持 JNI Library 生成
- 支持端到端,頭文件生成->JNI 庫生成
- 支持 Android APP 命令行編譯(測試 JNI Library)
- 支持端到端,頭文件生成->JNI 庫生成->APK 生成
如果你很熟悉 JNI 的 Native 函數命名規則,可以直接根據 Java 類手擼 Native 層函數原型命名(我干不了)。有了這個框架,編譯這一塊也搞定了,效率杠杠的,具體可以參見開源項目:https://github.com/yicm/BazelMixedLanguage
6 封裝思路和開發工具
思路:
涉及到開發工具如下:
- Vim + VS Code:代碼編輯
- Bazel:編譯
- adb:程序安裝和調試
- Android Studio:輔助創建工程和代碼編輯(可選)
7 后續高級擴展
- JNA
- https://github.com/java-native-access/jna
- 一個想法
- 直接通過 C 庫頭文件生成 Java 代碼,效率再次提升,就不折騰了。
8 小結
文章內容有點多,如果完整的啃下來,JNI 這塊就可以說 88 了。
本文涉及到了 JNI 封裝中常用的模版,嘔心瀝血,看完這么多模板,你都能發現其中規律了,就那么幾個操作。但是畢竟我只封裝了三次,歡迎補充和指正,一起來提高開發效率。另外歡迎嘗試 https://github.com/yicm/BazelMixedLanguage,其中包含 Java 層代碼,該開源項目絕對不會讓你失望的。
9 參考資料
- https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html
- https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp9502
- https://developer.android.google.cn/studio/profile/memory-profiler#jni-references
- https://developer.android.google.cn/training/articles/perf-jni
- https://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/performance.html
- https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#referencing_java_objects
- https://www.kancloud.cn/owenoranba/jni/120442
總結
以上是生活随笔為你收集整理的java 在已有的so基础上封装jni_[干货]再见,Android JNI 封装的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AITO品牌首款纯电动车 问界M5e更多
- 下一篇: 抢先iPhone 14 Pro感叹号设计