jni開發規範(什麼是JNI為什麼會有Native層)
2023-09-17 19:06:05 1
什麼是JNI?JNI是Java Native Interface的縮寫,通過使用 Java本地接口書寫程序,可以確保代碼在不同的平臺上方便移植;從Java1.1開始,JNI標準成為java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互
本地代碼與 Java 虛擬機之間是通過 JNI 函數實現相互操作的;JNI 函數通過接口指針來獲得,本地方法將 JNI 接口指針當作參數來接受;虛擬機保證在從相同的 Java 線程中對本地方法進行多次調用時,傳遞給本地方法的接口指針是相同的,本地方法被不同的 Java 線程調用時,它接受不同的 JNI接口指針
使用JNI和算法進行交互,主要是為了提高算法的性能,最大化的利用機器硬體資源
Android 中的語言層可以將語言層分成Java層和Native層;為什麼會有Native層呢?是因為在Java出現之前,很多的功能和系統都是由Native語言寫的,所以Java出現之後就不必重複造輪子,直接調用這些方法就行了,而且Native層更接近彙編語言,所以性能更優
所以代碼中就出現了Java層調用Native層的方法,而這個跨語言的調用過程就是 JNI
當Java語言無法處理一些任務的時候,就可以使用JNI來完成
下面是幾個 JNI 的應用場景:需要調用Java語言不支持的依賴於作業系統平臺特性的一些功能
● 需要調用當前UNIX系統的某個功能,而Java不支持這個功能的時候,就要用到JNI
● 在程序對時間敏感或對性能要求特別高時,有必要用到更底層的語言來提高運行效率
● 音視頻開發涉及到的音視頻編解碼需要更快的處理速度,這就需要用到JNI
● 為了整合一些以前的非Java語言開發的系統
● 需要用到早期實現的C/C 語言開發的一些功能或者系統,將這些功能整合到當前的系統或者新的版本中
JNI是完善Java的一個重要功能,它讓Java更加全面、封裝了各個平臺的差異性
jni異常處理在android ndk開發過程中,調用java對象方法可能會拋異常,如果在ndk層中不做任何處理,那麼程序就會直接崩潰;例如我們要在jni層獲取apk的籤名,代碼如下:
**jclass native_class = env->GetObjectClass(const_cast(contextObject)); jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager", "Landroid/content/pm/PackageManager;"); jobject pm_obj = env->CallObjectMethod(const_cast(contextObject), pm_id); jclass pm_clazz = env->GetObjectClass(pm_obj); jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); // 獲得應用包的信息 jobject pi_obj = NULL; //GET_SIGNATURES 64 pi_obj = env->CallObjectMethod(pm_obj, package_info_id, package_name, 64); ...**
上述代碼如果package_name查找不到就會報如下異常
捕獲異常
釋放資源
Native 提供了 ExceptionOccurred 和 ExceptionCheck 方法來檢測是否有異常發生,前者返回的是 jthrowable 類型,後者返回的是 jboolean 類型;如果有異常,會通過 ExceptionDescribe 方法來列印異常信息,方便我們在 LogCat 中看到對應的信息
而 ExceptionClear 方法則是關鍵的不會讓應用直接崩潰的方法,類似於 Java 的 catch 捕獲異常處理,它會消除這次異常;這樣就把由 Native 調用 Java 時的一個異常進行了處理,當處理完異常之後,別忘了釋放對應的資源
不過,我們這樣僅僅是消除了這次異常,還應該讓調用者有異常的發生,那麼就需要通過 Native 來拋出一個異常告訴 Java 調用者了
void throwByName(JNIEnv *env, const char *name, const char *msg) { jclass cls = env->FindClass(name); if (cls != NULL) { env->ThrowNew(cls, msg); } env->DeleteLocalRef(cls);}// 調用拋出異常extern "C"JNIEXPORT void JNICALLJava_com_glumes_cppso_jnioperations_ExceptionOps_nativeThrowException(JNIEnv *env, jobject instance) { throwByName(env, "java/lang/IllegalArgumentException", "native throw exception");}
jni錯誤日誌分析ndk發生崩潰的時候會在/data/tombstones文件夾下生成一個墓碑文件,形如tombstone_xx。谷歌也提供了addr2line/ objdump/ndk-stack工具
一個典型的墓碑文件內容如下
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***Build fingerprint: 'Android-x86/android_x86/x86:5.1.1/LMY48W/woshijpf04211939:eng/test-keys'Revision: '0'ABI: 'x86'pid: 1019, tid: 1019, name: surfaceflinger >>> /system/bin/surfaceflinger <<<signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x4 eax a6265c06 ebx b7467d88 ecx b7631a22 edx a6265c06 esi 00000000 edi b6867140 xcs 00000073 xds 0000007b xes 0000007b xfs 00000000 xss 0000007b eip b745a639 ebp bfcfc1e8 esp bfcfc150 flags 00010282backtrace: #00 pc 00006639 /system/lib/libui.so (android::Fence::waitForever(char const*) 41) #01 pc 00034b86 /system/lib/libsurfaceflinger.so #02 pc 0003229e /system/lib/libsurfaceflinger.so #03 pc 0002cb9c /system/lib/libgui.so (android::BufferQueue::ProxyConsumerListener::onFrameAvailable(android::BufferItem const&) 652) #04 pc 000342f4 /system/lib/libgui.so (android::BufferQueueProducer::queueBuffer(int, android::IGraphicBufferProducer::QueueBufferInput const&, android::IGraphicBufferProducer::QueueBufferOutput*) 2580) #05 pc 0004eafb /system/lib/libgui.so (android::Surface::queueBuffer(ANativeWindowBuffer*, int) 411) #06 pc 0004ce06 /system/lib/libgui.so (android::Surface::hook_queueBuffer(ANativeWindow*, ANativeWindowBuffer*, int) 38) #07 pc 00014bc6 /system/lib/egl/libGLES_android.so #08 pc 00017f73 /system/lib/egl/libGLES_android.so (eglSwapBuffers 163) #09 pc 00015fdb /system/lib/libEGL.so (eglSwapBuffers 203) #10 pc 000013ea /system/lib/hw/hwcomposer.x86.so #11 pc 00034730 /system/lib/libsurfaceflinger.so #12 pc 000256d4 /system/lib/libsurfaceflinger.so #13 pc 00024bf4 /system/lib/libsurfaceflinger.so #14 pc 000236fb /system/lib/libsurfaceflinger.so #15 pc 0002338a /system/lib/libsurfaceflinger.so #16 pc 0001e0ff /system/lib/libsurfaceflinger.so #17 pc 0001d9ce /system/lib/libutils.so (android::Looper::pollInner(int) 926) #18 pc 0001db73 /system/lib/libutils.so (android::Looper::pollOnce(int, int*, int*, void**) 67) #19 pc 0001e561 /system/lib/libsurfaceflinger.so #20 pc 00022ce7 /system/lib/libsurfaceflinger.so (android::SurfaceFlinger::run 39) #21 pc 00000ca3 /system/bin/surfaceflinger #22 pc 0001365a /system/lib/libc.so (__libc_init 106) #23 pc 00000da8 /system/bin/surfaceflingerstack: bfcfc110 00000000 bfcfc114 b6839270 bfcfc118 00000000 bfcfc11c 00000000 bfcfc120 b68394e0 bfcfc124 00000002 bfcfc128 00000002 bfcfc12c b75d8185 /system/lib/libutils.so (android::RefBase::incStrong(void const*) const 53) bfcfc130 b6839270 bfcfc134 bfcfc1e8 [stack] bfcfc138 00000002 bfcfc13c a6265c06 bfcfc140 b7467d88 /system/lib/libui.so bfcfc144 00000000 bfcfc148 b6867140 bfcfc14c b745a639 /system/lib/libui.so (android::Fence::waitForever(char const*) 41) #00 bfcfc150 b683af18 bfcfc154 bfcfc1e8 [stack] bfcfc158 00000000 bfcfc15c 00000000 bfcfc160 00000000 bfcfc164 b683af18 bfcfc168 b75ec9c4 /system/lib/libutils.so bfcfc16c b75d8285 /system/lib/libutils.so (android::RefBase::weakref_type::decWeak(void const*) 37) bfcfc170 00000000 bfcfc174 00000000 bfcfc178 00000000 bfcfc17c 00000000 bfcfc180 b7642968 /system/lib/libsurfaceflinger.so bfcfc184 bfcfc1e8 [stack] bfcfc188 b6867140 bfcfc18c b7622b87 /system/lib/libsurfaceflinger.so
文件結構
qinqundeMacBook-Pro% ./aarch64-linux-android-addr2line -C -f -e ~/Documents/DProtect2/mylibrary/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so 0000000000157efcJava_com_dofun_dprotect_lib_LocalWork_start/Users/qinqun/Documents/DProtect2/mylibrary/src/main/cpp/native-lib.cpp:147
ndk-stack有需要完整代碼的同學 可以 私信 發送 「底層源碼」 即可 免費獲取
現在發送還可以獲得更多《Android 學習筆記+源碼解析+面試視頻》
JNI 特點:二進位兼容
● 本地方法庫與同一平臺上所有Java 虛擬機之間實現二進位兼容,即對於給定平臺開發人員只需要維護一種版本的本地方法庫
效率高
為了實現實時系統,JNI 在效率與虛擬機無關性之間進行了優化,以保障高效運行
功能強
● JNI 提供了大量的函數及接口讓本地方法與Java 虛擬機內核相互操作,增強兩者的功能
本地代碼與 Java 虛擬機之間是通過 JNI 函數實現相互操作的;JNI 函數通過接口指針來獲得,本地方法將 JNI 接口指針當作參數來接受;虛擬機保證在從相同的 Java 線程中對本地方法進行多次調用時,傳遞給本地方法的接口指針是相同的,本地方法被不同的 Java 線程調用時,它接受不同的 JNI接口指針
尾述有需要完整代碼的同學 可以 私信 發送 「底層源碼」 即可 免費獲取
現在發送還可以獲得更多《Android 學習筆記+源碼解析+面試視頻》
技術是無止境的,你需要對自己提交的每一行代碼、使用的每一個工具負責,不斷挖掘其底層原理,才能使自己的技術升華到更高的層面
Android 架構師之路還很漫長,與君共勉
PS:有問題歡迎指正,可以在評論區留下你的建議和感受;
歡迎大家點讚評論,覺得內容可以的話,可以轉發分享一下
,