【Android NDK 开发】JNI 动态注册 ( 动态注册流程 | JNI_OnLoad 方法 | JNINativeMethod 结构体 | GetEnv | RegisterNatives )
文章目錄
- I . 動態注冊流程 ( 總結 )
- II . JNI_OnLoad 方法
- III . 被注冊的本地 C/C++ 方法參數
- IV . JNINativeMethod 結構體 ( 核心重點 )
- V . JavaVM 獲取 JNIEnv ( GetEnv )
- VI . 動態注冊方法 RegisterNatives ( 核心重點 )
- VII . 動態注冊流程完整代碼
I . 動態注冊流程 ( 總結 )
動態注冊流程 :
① 聲明 Java 層 Native 方法 : 在 Java 類中聲明 native 方法 ;
/*** 動態注冊 JNI 方法 , Java 方法*/public native void dynamicRegisterJavaMethod();public native int dynamicRegisterJavaMethod2(int i);② 準備數據 JNINativeMethod methods[] 數組 : 其中定義了 Java 層方法與 Native 層方法的對應關系 ;
/*該數組中 , 每個元素都是一個 JNI 的 Native 方法JNINativeMethod 是結構體typedef struct {const char* name; //Java 中定義的 Native 方法名 , 注意這是一個 C 字符串const char* signature; //函數簽名 , 可以使用 javap 生成void* fnPtr; //C/C++ 中的 Native 函數簽名} JNINativeMethod;*/ static const JNINativeMethod methods[] = {{"dynamicRegisterJavaMethod", "()V", (void *)dynamicRegisterCMethod},{"dynamicRegisterJavaMethod2", "(I)I", (void *)dynamicRegisterCMethod2} };③ 編寫 JNI_OnLoad 方法 : 在該方法中進行 JNI 方法動態注冊操作 ;
int JNI_OnLoad(JavaVM *vm , void *r){return JNI_VERSION_1_6; }④ 獲取 JNIEnv 指針 : 調用 JavaVM 結構體的 GetEnv 方法 , 獲取 JNIEnv 指針 ;
//1 . 獲取 JNIEnv JNI 環境 , 需要從 JavaVM 獲取JNIEnv *env = nullptr;//2 . 調用 JavaVM / _JavaVM 結構體的 jint GetEnv(void** env, jint version) 方法// 返回值分析 : 動態注冊會返回一個結果// 如果 registerResult < 0 , 則動態注冊失敗// 如果 registerResult == 0 , 則動態注冊失敗int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );⑤ 獲取 Java 類 : 調用 JNIEnv 結構體的 FindClass 方法獲取 jclass 對象 ;
/*動態注冊的 Java 類名稱注意 : 包名類名之間使用 "/" 分割*/ static const char* className = "kim/hsl/onload/MainActivity";//獲取要動態注冊的 Java 類的 Class 對象jclass jclazz = env->FindClass(className);⑥ 進行動態注冊 : 調用 JNIEnv 的 RegisterNatives 方法 , 進行正式注冊 ;
/*5 .正式注冊注冊方法解析 :jint RegisterNatives(jclass clazz, //要注冊的 Java 類const JNINativeMethod* methods, //JNI注冊方法數組jint nMethods //要注冊的 JNI 方法個數)sizeof(methods) / sizeof(JNINativeMethod) : 計算 JNINativeMethod methods[] 數組大小*/env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));II . JNI_OnLoad 方法
1 . JNI_OnLoad 函數原型 : Java 類中調用 System.loadLibrary(“native-lib”) 代碼時 , 調用 JNI_OnLoad 方法 ;
① jni.h 中有該函數的聲明 :
/** Prototypes for functions exported by loadable shared libs. These are* called by JNI, not provided by JNI.*/ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);② 參數列表說明 :
JavaVM* vm : 表示 Java 虛擬機 ;
void* reserved : 一般是 NULL ;
③ 返回值說明 : 返回當前 NDK 使用的 JNI 版本 ;
JNI 版本 中可選的有四個值 , 但是只能選擇返回后三個 JNI_VERSION_1_2 , JNI_VERSION_1_4 , JNI_VERSION_1_6 , 返回上述三個值任意一個沒有區別 ;
返回 JNI_VERSION_1_1 會報錯 ;
#define JNI_VERSION_1_1 0x00010001 #define JNI_VERSION_1_2 0x00010002 #define JNI_VERSION_1_4 0x00010004 #define JNI_VERSION_1_6 0x000100062 . JNI_OnLoad 方法常用操作 :
① 獲取 JavaVM 對象 ;
② 動態注冊 JNI 方法 ;
III . 被注冊的本地 C/C++ 方法參數
1 . 動態注冊對應的 C/C++ 本地方法 參數情況 :
① 傳遞參數 : 如果動態注冊的方法需要傳遞參數 , 需要加上 前面的 JNIEnv *env, jobject obj 兩個參數
jint dynamicRegisterCMethod2(JNIEnv *env, jobject obj, jint i){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod2 : %d", i);return i + 1; }② 不傳遞參數 : 如果不傳遞參數 , 就可以不添加任何參數 , 參數可以空著 ;
void dynamicRegisterCMethod(){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod");}IV . JNINativeMethod 結構體 ( 核心重點 )
1 . 結構體定義 : 該結構體定義了 C/C++ 方法 與 Java 方法的映射 ;
① const char* name : Java 中定義的 Native 方法名 , 注意這是一個 C 字符串
② const char* signature : 函數簽名 , 可以使用 javap 生成
③ void* fnPtr : C/C++ 中的 Native 函數指針
typedef struct {const char* name; // Java 中定義的 Native 方法名 , 注意這是一個 C 字符串const char* signature; // 函數簽名 , 可以使用 javap 生成void* fnPtr; // C/C++ 中的 Native 函數簽名 } JNINativeMethod;2 . JNINativeMethod methods[] 數組 :
該數組是 JNI 方法動態注冊的參數 , 每個結構體表示了本地方法 與 Java 層方法的映射 ;
數組中有幾個元素 , 那么就映射了幾個方法 ;
V . JavaVM 獲取 JNIEnv ( GetEnv )
函數原型 : 從 Java 虛擬機 ( JavaVM ) 中獲取 JNI 運行環境 ( JNIEnv ) ;
① 參數說明 :
void** env : JNIEnv 的二級指針 ;
jint version : JNI 的版本 , 一般傳入 JNI_VERSION_1_6 ;
② 返回值說明 : 返回動態注冊結果 ;
動態注冊成功 : 返回 JNI_OK , 即 0 ;
動態注冊失敗 : 返回一個小于 0 的值 ;
struct _JavaVM {//封裝了 JNIInvokeInterface 結構體 , C 語言環境中調用該結構體中的方法const struct JNIInvokeInterface* functions;#if defined(__cplusplus)...//C++ 中封裝了 JNIInvokeInterface 的 GetEnv 方法jint GetEnv(void** env, jint version){ return functions->GetEnv(this, env, version); }... #endif /*__cplusplus*/ };VI . 動態注冊方法 RegisterNatives ( 核心重點 )
1 . 函數原型 : 通過該 RegisterNatives 方法注冊 JNI 方法 ;
參數 :
- jclass clazz : 要注冊方法所在的 Java 類
- const JNINativeMethod* methods : Java 方法 與 本地方法的映射關系數組 ;
- jint nMethods : 映射的方法個數 , 一般是 methods 數組元素個數 ;
2 . 代碼示例 :
//1 . Java 與 本地 方法映射數組 JNINativeMethod methods[] = {{"dynamicRegisterJavaMethod", "()V", (void *)dynamicRegisterCMethod},{"dynamicRegisterJavaMethod2", "(I)I", (void *)dynamicRegisterCMethod2} }; ... //2 . Java 類名 , 注意 : 包名類名之間使用 "/" 分割 char* className = "kim/hsl/onload/MainActivity"; ... //3 . 獲取要動態注冊的 Java 類的 Class 對象 jclass jclazz = env->FindClass(className); ... //4 . 正式注冊 env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));VII . 動態注冊流程完整代碼
1 . Java 層代碼 :
package kim.hsl.onload;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle; import android.widget.TextView;public class MainActivity extends AppCompatActivity {// Used to load the 'native-lib' library on application startup.static {System.loadLibrary("native-lib");}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);dynamicRegisterJavaMethod();dynamicRegisterJavaMethod2(250);}/*** 動態注冊 JNI 方法 , Java 方法*/public native void dynamicRegisterJavaMethod();public native int dynamicRegisterJavaMethod2(int i); }2 . 本地代碼 ( C++ ) :
#include <jni.h> #include <string> #include <android/log.h>/*I . JNI_Onload 方法JNI_Onload 方法在 Java 層執行 System.loadLibrary("native-lib") 代碼時調用的方法主要是執行一些 JNI 初始化操作JNI_Onload 常見操作 :① 保存 JavaVM 對象 , 使用全局變量記錄該 Java 虛擬機對象② 動態注冊 : 動態注冊是該方法中最常見的操作JNI_Onload 參數說明 :JavaVM *vm : 代表了 Java 虛擬機void *r : 一般是 NULLJNI_Onload 返回值說明 :int 類型返回值代表了當前 NDK 使用的 JNI 版本JNI 版本 中可選的有四個值 , 但是只能選擇返回后三個 JNI_VERSION_1_2 , JNI_VERSION_1_4 , JNI_VERSION_1_6返回上述三個值任意一個沒有區別返回 JNI_VERSION_1_1 會報錯#define JNI_VERSION_1_1 0x00010001#define JNI_VERSION_1_2 0x00010002#define JNI_VERSION_1_4 0x00010004#define JNI_VERSION_1_6 0x00010006這四個值分別代表了 JDK 1.1 , 1.2 , 1.4 , 1.6 對應的 JNI 版本II . 動態注冊動態注冊 :動態注冊與靜態注冊 :靜態注冊 : 使用 Java_包名_類名_方法名(JNIEnv* env, jobject obj, ...) 方式進行注冊是靜態注冊動態注冊 : 將 C/C++ 中的本地方法 與 Java 中的方法對應起來 , 就需要使用動態注冊動態注冊 與 靜態注冊 : 沒有太大區別 , 都可以將 C/C++ 本地方法 與 Java 方法對應起來動態注冊流程 :① 聲明 Java 層 Native 方法② 準備數據 JNINativeMethod methods[] 數組③ 編寫 JNI_OnLoad 方法④ 獲取 JNIEnv 指針⑤ 獲取 Java 類⑥ 進行動態注冊*///使用 全局變量 記錄 Java 虛擬機對象 JavaVM *_vm;/*動態注冊對應的 C/C++ 本地方法如果動態注冊的方法需要傳遞參數 , 需要加上 前面的 JNIEnv *env, jobject obj 兩個參數如果不傳遞參數 , 就可以不添加任何參數不傳遞參數 , 參數可以空著*/ void dynamicRegisterCMethod(){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod");}/*動態注冊對應的 C/C++ 本地方法如果動態注冊的方法需要傳遞參數 , 需要加上 前面的 JNIEnv *env, jobject obj 兩個參數如果不傳遞參數 , 就可以不添加任何參數傳遞參數 , 那么需要寫上 JNI 調用的完整參數*/ jint dynamicRegisterCMethod2(JNIEnv *env, jobject obj, jint i){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod2 : %d", i);return i + 1; }/*該數組中 , 每個元素都是一個 JNI 的 Native 方法JNINativeMethod 是結構體typedef struct {const char* name; //Java 中定義的 Native 方法名 , 注意這是一個 C 字符串const char* signature; //函數簽名 , 可以使用 javap 生成void* fnPtr; //C/C++ 中的 Native 函數指針} JNINativeMethod;*/ static const JNINativeMethod methods[] = {{"dynamicRegisterJavaMethod", "()V", (void *)dynamicRegisterCMethod},{"dynamicRegisterJavaMethod2", "(I)I", (void *)dynamicRegisterCMethod2} };/*動態注冊的 Java 類名稱注意 : 包名類名之間使用 "/" 分割*/ static const char* className = "kim/hsl/onload/MainActivity";int JNI_OnLoad(JavaVM *vm , void* reserved){__android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "JNI_Onload");//I . 存儲 Java 虛擬機對象//將 Java 虛擬機對象記錄到全局變量中_vm = vm;//II . 動態注冊//1 . 獲取 JNIEnv JNI 環境 , 需要從 JavaVM 獲取JNIEnv *env = nullptr;//2 . 調用 JavaVM / _JavaVM 結構體的 jint GetEnv(void** env, jint version) 方法// 返回值分析 : 動態注冊會返回一個結果// 如果 registerResult < 0 , 則動態注冊失敗// 如果 registerResult == 0 , 則動態注冊失敗int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );//3 . 判斷結果 : 如果動態注冊 Native 方法失敗 , 直接退出if(registerResult != JNI_OK){return -1;}//4 . 獲取要動態注冊的 Java 類的 Class 對象jclass jclazz = env->FindClass(className);/*5 .正式注冊注冊方法解析 :jint RegisterNatives(jclass clazz, //要注冊的 Java 類const JNINativeMethod* methods, //JNI注冊方法數組jint nMethods //要注冊的 JNI 方法個數)sizeof(methods) / sizeof(JNINativeMethod) : 計算 JNINativeMethod methods[] 數組大小*/env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));return JNI_VERSION_1_6; }3 . 執行結果 :
2020-02-08 14:01:51.142 13375-13375/? I/JNI_TAG: JNI_Onload 2020-02-08 14:01:51.299 13375-13375/? I/JNI_TAG: dynamicRegisterCMethod 2020-02-08 14:01:51.300 13375-13375/? I/JNI_TAG: dynamicRegisterCMethod2 : 250總結
以上是生活随笔為你收集整理的【Android NDK 开发】JNI 动态注册 ( 动态注册流程 | JNI_OnLoad 方法 | JNINativeMethod 结构体 | GetEnv | RegisterNatives )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 应用开发】多点触控 (
- 下一篇: 【Android NDK 开发】JNI