Java JNI的幾部曲以及JNI的方法綁定關係(靜態/動態)

編編成程 發佈 2022-12-18T21:58:19.769037+00:00

簡單來說需要有3個庫來做輔助,除去客戶端本身的之外,按照下面的關係淺黃色裡面都是我們要準備的,除去本身的C庫之外,我們要寫一個JNI的庫,同時還要有一個對應的Java程序,在他的主程序中進行load()同時注意上面的native關鍵字,表示都是想通過JNI進行暴露的結合上面這張圖

簡單來說需要有3個庫來做輔助,除去客戶端本身的之外,按照下面的關係


淺黃色裡面都是我們要準備的,除去本身的C庫之外,我們要寫一個JNI的庫,同時還要有一個對應的Java程序,在他的主程序中進行load()


同時注意上面的native關鍵字,表示都是想通過JNI進行暴露的



結合上面這張圖我們重新Refine一下:

  • MediaRecorder - 是我們能夠接觸到的Java類,但是他的內部會通過LoadLibrary來打開下面的JNI的庫。對於Java層來說,他的主要工作是加載庫,同時擁對應的native來聲明他所想要調用在JNI的函數就可以了
  • libmedia_jni.so - 是真正的JNI庫,他負責攜手對應的Java以及相應的native library. 他是cpp類,同時是JNI的真正實現 可以看到對於靜態函數init來說,他的形參只有JNIEnv, 對於實例函數start()來說,他的形參有JNIEnv以及jobject兩個。
  • libmedia.so - 真正實現功能的native庫


JNI方法註冊

JNI方法註冊其實說白了就是綁定,他分為動態和靜態兩種,其中靜態註冊用於NDK開發,動態註冊用於Framework開發


靜態註冊:

先準備一個簡單的Java類,他需要有對應的native來標註想從JNI調用的函數


通過javac以及javah來刷出JNI的頭文件


得到下面的代碼


接下來我們來關注兩者是如何註冊的關係,我們以函數Java_com_example_MediaRecorder_native_1init為例,Java_表示這是從Java平台調用JNI方法,後面的com_example_MediaRecorder_native_1init指的是包名+類名+方法名。還需要注意的一點是,原來在Java中的"."在這裡變成了"_",因為.在C中有特殊含義。另外還有之前的接口裡藏了一個"1",這是因為本身的native_int()有一個"_",因此這裡要變成"_1"


其中JNIEnv *是一個指向全部JNI方法的指針,該指針只在創建它的線程有效,不能跨線程傳遞。jclass是JNI的數據類型,對應Java的java.lang.Class. jobject同樣也是數據類型,對應Java的Object.


當我們從Java中調用native_init的時候,就會從JNI中尋找Java_com_example_MediaRecorder_native_1init方法,如果沒有就報錯。如果找到,就讓native_init與Java_com_example_MediaRecorder_native_1init建立關聯,其實就是保存此時JNI的方法指針,再次調用nativie_init()的時候後就直接用這個方法指針就行了。


靜態註冊說白了就是根據方法名和隱式規則讓Java方法和JNI建立關聯,但是它有如下的一些缺點:

  • JNI層的方法名稱過長
  • 聲明Native方法的類使用javah生成頭文件
  • 初次調用JNI方法會建立關聯,影響效率


靜態註冊就是讓Java的Native方法通過方法指針與JNI進行關聯,如果Native方法知道他在JNI中的方法指針,就可以避免上述的性能損耗,這就是動態註冊


動態註冊:

JNI中有一種結構可以記錄Java的Native方法和JNI的方法的關聯關係,他就是JNINativeMethod,他被如下定義



因此你需要在你的JNI層定義下面的結構


上面定義了gMethods數組,裡面存儲的是Native方法和JNI方法的關聯,()V表示start的方法簽名。當然只定義JNINativeMethod類型是沒有用的。還需要註冊他,註冊方法為register_android_media_MediaRecorder:


可以看到最終調用的是registerNativeMethods(), 他最終會調用JNI的這個函數


最終的最終是調用JNIEnv的這個方法


同時需要注意在最先開始的時候有一個注釋,表示自己的調用會在JNI_OnLoad()的時候被調用


這個JNI_OnLoad會在System.LoadLibrary()的時候被調用

關鍵字: