【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )
文章目錄
- I . 調用 Java 方法流程
- II . 獲取 jclass 對象 ( GetObjectClass )
- III . 獲取 jclass 對象 ( FindClass )
- IV . JNI 函數簽名規則
- V . javap 獲取函數簽名 ( 推薦 )
- VI . 反射獲取對象方法 ( GetMethodID )
- VII . 反射獲取類靜態方法 ( GetStaticMethodID )
- VIII . 調用 Java 對象方法 ( CallXxxMethod )
- IX . 調用 Java 類靜態方法 ( CallStaticXxxMethod )
- X . 完整代碼示例
I . 調用 Java 方法流程
JNI 中調用 Java 方法流程 :
① 獲取 jclass 類型變量 :
調用 jclass GetObjectClass(jobject obj) 或 jclass FindClass(const char* name) 方法 , 獲取 jclass 類型變量 ;
② 通過反射獲取方法 :
調用 jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) 獲取 Java 對象方法 ,
調用 jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig) 獲取 Java 類的靜態方法 ;
③ 調用方法 :
void CallVoidMethod(jobject obj, jmethodID methodID, …) 調用 Java 對象方法 ,
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, …) 調用 Java 的靜態方法 ;
CalXxxMethod 方法 , 其中的 Xxx 是 Java 對象的 返回值 , 不同的返回值調用不同的方法 ;
II . 獲取 jclass 對象 ( GetObjectClass )
1 . 函數原型 : 通過傳入 Java 對象 ( jobject 類型變量 ) , 獲取 Java 類對象 ( jclass 類型變量 )
返回值 : 返回 Java 字節碼 Class 對象 , 對應 C/C++ 中的 jclass 對象 ;
參數 : 傳入 Java 對象 ; ( 該對象一般是由 JNI 方法傳入的 )
2 . 代碼示例 :
extern "C" JNIEXPORT void JNICALL Java_kim_hsl_jni_MainActivity_jniObjectTest(JNIEnv *env, jobject instance, jobject student) {//獲取 Java 對應的 Class 對象jclass student_class = env->GetObjectClass(student);... }III . 獲取 jclass 對象 ( FindClass )
函數原型 : 通過傳入完整的 包名.類名 獲取 Java 類對應的 C/C++ 環境下的 jclass 類型變量 ;
返回值 : 返回 Java 字節碼 Class 對象 , 對應 C/C++ 中的 jclass 對象 ;
參數 : 傳入 完整的 包名/類名 , 注意包名中使用 “/” 代替 “.” , 如 “kim/hsl/jni/Teacher” ;
2 . 代碼示例 : 獲取 kim.hsl.jni.Teacher 對應的 jclass 對象 ;
extern "C" JNIEXPORT void JNICALL Java_kim_hsl_jni_MainActivity_jniObjectTest(JNIEnv *env, jobject instance, jobject student) {...// 5.2 獲取 Teacher 類 ( 該變量需要釋放 )jclass class_teacher = env->FindClass("kim/hsl/jni/Teacher");... }IV . JNI 函數簽名規則
參考 : JNI 函數簽名規則
V . javap 獲取函數簽名 ( 推薦 )
自己寫函數簽名容易出錯 , 還麻煩 , 推薦使用 javap 工具 ;
1 . 字節碼文件 : 首先要先編譯出 Student 的 class 字節碼文件 , javap 命令要直接作用于該字節碼文件 ;
2 . Android Studio 中 Java 代碼編譯后的 class 字節碼文件位置 : 不同版本的 AS 編譯后的字節碼位置不同 , 建議在各自的 Module 下的 build 目錄中進行文件查找 , 找到 class 字節碼所在目錄 ;
3 . 我的 AS 中目錄位置是 : Y:\002_WorkSpace\001_AS\001_NDK_Hello\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes , 在這個目錄下執行 javap -s kim.hsl.jni.Student 命令 , 即可獲取類中的函數簽名 ;
4 . javap 命令格式 : javap -s 完整包名.類名 ;
如 : 要獲取 kim.hsl.jni.Student 類中的函數簽名 , 使用 javap -s kim.hsl.jni.Student 命令 ;
5 . 執行命令 : 在 class 目錄下執行 javap -s kim.hsl.jni.Student 命令 ;
Y:\002_WorkSpace\001_AS\001_NDK_Hello\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes>javap -s kim.hsl.jni.Student Compiled from "Student.java" public class kim.hsl.jni.Student {public kim.hsl.jni.Student();descriptor: ()Vpublic kim.hsl.jni.Student(int, java.lang.String);descriptor: (ILjava/lang/String;)Vpublic int getAge();descriptor: ()Ipublic void setAge(int);descriptor: (I)Vpublic java.lang.String getName();descriptor: ()Ljava/lang/String;public void setName(java.lang.String);descriptor: (Ljava/lang/String;)V }VI . 反射獲取對象方法 ( GetMethodID )
函數原型 : 通過 jclass 對象 , 方法名稱 , 和 方法簽名 , 獲取 Java 類對象對應的方法 ID 即 jmethodID 類型變量 ;
返回值 : Java 類對象對應的方法 ID ( jmethodID 類型變量 )
參數 :
- jclass clazz : 要獲取的 Java 對象方法對應的 Java 類對象 ;
- const char* name : 方法名稱 ;
- const char* sig : 方法簽名 , 使用 javap 命令獲得 ;
2 . 代碼示例 : 獲取 Student 類的 getAge 方法 ;
student_class 是 Student 類對應的 C/C++ 中的 jclass 類型變量 ,
“getAge” 是方法名稱 ,
“()I” 是方法簽名 , 左側的括號是參數列表類型簽名 , 括號右的 I 是返回值類型 int ;
VII . 反射獲取類靜態方法 ( GetStaticMethodID )
函數原型 : 通過 jclass 對象 , 方法名稱 , 和 方法簽名 , 獲取 Java 類對象對應的方法 ID 即 jmethodID 類型變量 ;
返回值 : Java 類對象對應的方法 ID ( jmethodID 類型變量 )
參數 :
- jclass clazz : 要獲取的 Java 對象方法對應的 Java 類對象 ;
- const char* name : 方法名稱 ;
- const char* sig : 方法簽名 , 使用 javap 命令獲得 ;
2 . 代碼示例 : 獲取 Student 類的 getAge 方法 ;
student_class 是 Student 類對應的 C/C++ 中的 jclass 類型變量 ,
“logInfo” 是方法名稱 ,
“(Ljava/lang/String;)V” 是方法簽名 , 左側的括號是參數列表類型簽名 , 括號右的 V 是返回值類型 void ;
VIII . 調用 Java 對象方法 ( CallXxxMethod )
注意 : 返回值和參數必須 都是 Java 類型 ;
函數原型 : 通過 Java 對象 , Java 方法 ID , 及根據函數簽名傳入的 參數列表 ( 可變參數 ) , 反射調用該 Java 對象的方法 ;
返回值 : Void , 注意這里的返回值可以是 8 種 基本數據類型 , jboolean , jbyte , jshort 等類型 , 也可以是引用類型 jobject 類型 , 只有這 10 種返回類型 , 沒有其它類型 ; ( 注意 : 返回值 都是 Java 類型 )
參數 :
- jobject obj : 要獲取的 Java 對象方法對應的 Java 類對象 ;
- jmethodID methodID : 方法 ID ;
- … : 可變參數 , 方法的參數 ( 注意 : 參數 必須 都是 Java 類型 ) ;
2 . 所有 Java 方法調用返回值類型 :
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list);jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list);jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);3 . 代碼示例 : 調用 Student 類的 getAge 方法 ;
student 是 Student 類對象 ,
“method_getAge” 是方法 ID
由于沒有參數 , 因此后面的可變參數列表為空 ;
IX . 調用 Java 類靜態方法 ( CallStaticXxxMethod )
注意 : 返回值和參數必須 都是 Java 類型 ;
函數原型 : 通過 Java 類對象 ( Class 對象 對應 C/C++ jclass 類型對象 ) , Java 方法 ID , 及根據函數簽名傳入的 參數列表 ( 可變參數 ) , 反射調用該 Java 對象的方法 ;
返回值 : Void , 注意這里的返回值可以是 8 種 基本數據類型 , jboolean , jbyte , jshort 等類型 , 也可以是引用類型 jobject 類型 , 只有這 10 種返回類型 , 沒有其它類型 ; ( 注意 : 返回值 都是 Java 類型 )
參數 :
- jobject obj : 要獲取的 Java 對象方法對應的 Java 類對象 ;
- jmethodID methodID : 方法 ID ;
- … : 可變參數 , 方法的參數 ( 注意 : 參數 必須 都是 Java 類型 ) ;
2 . 所有 Java 方法調用返回值類型 :
jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list);jobject (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...);jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,va_list);jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);jbyte (*CallStaticByteMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...);jchar (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list);jchar (*CallStaticCharMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...);jshort (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list);jshort (*CallStaticShortMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...);jint (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list);jint (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...);jlong (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list);jlong (*CallStaticLongMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...);jfloat (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list);jfloat (*CallStaticFloatMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...);jdouble (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list);jdouble (*CallStaticDoubleMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);void (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);3 . 代碼示例 : 調用 Student 類的 logInfo 方法 ;
student 是 Student 類對象 ,
“method_logInfo” 是方法 ID
info : jstring 類型參數 , 傳入字符串到 Java 層運行 ( 注意 : 參數 必須 都是 Java 類型 ) ;
X . 完整代碼示例
完整代碼示例 :
extern "C" JNIEXPORT void JNICALL Java_kim_hsl_jni_MainActivity_jniObjectTest(JNIEnv *env, jobject instance, jobject student) {/*參數解析 :JNIEnv *env : JNI 環境 , 結構體指針 , 結構體中封裝了 209 個方法jobject instance : 是 MainActivity 對象jobject student : Java 層創建的 Student 對象 , 傳入 Native 層*///在 C/C++ 中調用 Student 對象的 get 方法//1 . 獲取 Java 對應的 Class 對象jclass student_class = env->GetObjectClass(student);//2 . 通過 Class 的反射獲取要調用的方法/*函數原型 :jmethodID GetMethodID(jclass clazz, const char* name, const char* sig){ return functions->GetMethodID(this, clazz, name, sig); }參數說明 :jclass clazz : 使用 GetObjectClass 方法獲取的返回值const char* name : 要調用的方法名稱const char* sig : 函數簽名 , 具體的簽名規則查看簽名表格public int getAge() 函數簽名 : ()I左側的 () : 左側的括號是參數列表類型簽名右側的 I : 括號右的 I 是返回值類型 intpublic void setAge(int age) 函數簽名 : (I)V(I) 表示左側的參數列表右側的 V 表示其返回值類型是 void 類型引用類型簽名 : L + 全限定名 + ;javap 工具 :可以使用 javap 工具獲取方法簽名*///獲取 Student 的 public int getAge() 方法jmethodID method_getAge = env->GetMethodID(student_class, "getAge" , "()I");//獲取 Student 的 public void setAge(int age) 方法jmethodID method_setAge = env->GetMethodID(student_class, "setAge" , "(I)V");//獲取 Student 的 public static void logInfo(String info) 方法// 注意這里要使用 GetStaticMethodID 方法反射該靜態方法jmethodID method_logInfo = env->GetStaticMethodID(student_class, "logInfo" , "(Ljava/lang/String;)V");//3 . 調用 Java 對象的方法/*調用 Java 引用對象的方法 : 要根據 返回值類型不同 , 調用不同的方法如果返回值是 int 類型 , 那么就需要調用 CallIntMethod 方法如果返回值是 void 類型 , 那么就需要調用 CallVoidMethod 方法如果調用的是靜態方法 , 那么需要調用( 注意 : 調用方法時傳入的參數都必須是 C/C++ 中的 Java 類型參數 , 如 jint , jstring 等 )*///調用 Student 對象的 public void setAge(int age) 方法env->CallVoidMethod(student, method_setAge, 18);//調用 Student 的 public int getAge() 方法jint age = env->CallIntMethod(student, method_getAge);/*調用靜態方法 :1 . 創建 Java 字符串2 . 調用靜態方法3 . 釋放 Java 字符串*/// 創建 Java 字符串jstring info = env->NewStringUTF("C/C++ 創建的 Java 字符串");// 調用靜態方法 : 注意傳入的參數env->CallStaticVoidMethod(student_class, method_logInfo, info);// jstring info 在方法中創建新的字符串 , 需要在方法結束之前釋放該引用對象env->DeleteLocalRef(info);//4 . 設置 Student 對象屬性/*反射獲取屬性函數原型 :jfieldID GetFieldID(jclass clazz, const char* name, const char* sig){ return functions->GetFieldID(this, clazz, name, sig); }參數說明 :jclass clazz : Java 類對象const char* name : 屬性名稱const char* sig : 屬性類型簽名設置反射屬性值 :函數原型 :void SetIntField(jobject obj, jfieldID fieldID, jint value){ functions->SetIntField(this, obj, fieldID, value); }參數說明 :jobject obj : 設置對象jfieldID fieldID : 通過 GetFieldID 方法獲取的屬性 IDjint value : 要設置的值注意 : 設置不同類型的值 , 調用不同的設置方法*/jfieldID age_field_id = env->GetFieldID(student_class, "age", "I");env->SetIntField(student, age_field_id, 90);// 驗證是否設置成功age = env->CallIntMethod(student, method_getAge);//5 . 在 JNI 中創建 java 對象 , 并設置給另一個對象/*獲取 Teacher 類 : 通過調用 FindClass 方法獲取 Teacher 類目前已知兩種獲取 jclass 的方法獲取 Teacher 類的構造方法 public Student(int age, String name)構造方法的方法名都是 "<init>"構造方法的函數簽名為此處還要特別注意 : 傳入到 Java 方法中的參數 , 必須都是 Java 參數類型如 jstring , jint , jintArray 等類型 , 不能將 C/C++ 類型傳入參數尤其是 char* 字符串 , 需要使用 NewStringUTF 將 C/C++ 字符串轉為 jstring 類型字符串創建 Teacher 對象將 Teacher 對象設置給 Student 對象*/// 5.1 獲取 Student 的 public void setTeacher(Teacher teacher) 方法// 注意這個 Teacher 的類型簽名是 Lkim/hsl/jni/Teacher;jmethodID method_setTeacher = env->GetMethodID(student_class, "setTeacher" , "(Lkim/hsl/jni/Teacher;)V");LOGE("method_setTeacher");// 5.2 獲取 Teacher 類 ( 該變量需要釋放 )jclass class_teacher = env->FindClass("kim/hsl/jni/Teacher");// 5.3 查找構造方法jmethodID method_init = env->GetMethodID(class_teacher, "<init>", "(ILjava/lang/String;)V");// 5.4 準備 Java 類型參數 ( 該變量需要釋放 )// 此處特別注意 : 傳入到 Java 方法中的參數都必須是 Java 參數jint teacher_age = 88;jstring teacher_name = env->NewStringUTF("Tom Wang");// 5.5 創建 Teacher 對象 ( 該變量需要釋放 )jobject teacher = env->NewObject(class_teacher, method_init, teacher_age, teacher_name);// 5.6 調用 Student 對象的 setTeacher 設置方法env->CallVoidMethod(student, method_setTeacher, teacher);// 5.7 釋放上面通過 FindClass NewStringUTF NewObject 創建的引用變量 , 便于節省內存 , 也可以等到 作用域結束 自動釋放// 使用完這三個引用之后 , 不再使用 ; 這里特別建議手動釋放三個引用// 如果不手動釋放 , 在 該引用 作用域 結束后 , 也會自動釋放掉env->DeleteLocalRef(teacher_name);env->DeleteLocalRef(teacher);env->DeleteLocalRef(class_teacher);}總結
以上是生活随笔為你收集整理的【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android NDK 开发】JNI
- 下一篇: 【Android NDK 开发】JNI