JNI進階一 (C++呼叫java屬性和方法,javap的使用)
一、C/C++函式分析:
//獲取jclass物件,引數:this的意思,就是native方法所在的類
1.GetObjectClass(jobject)
//獲取普通屬性id,第一個引數:類物件, 第二個引數:屬性名,第三個引數:屬性簽名(不知道的同學點選這裡)
2.GetFieldID(jclass clazz, const char* name, const char* sig)
//設定int屬性的值, 第一個引數:this的意思, 第二個引數:獲取屬性id, 第三個引數:要設定的值
3.SetIntField(jobject obj, jfieldID fieldID, jint value)
當然這裡就只列舉SetIntField函數了,同理還有很多,比如:SetCharField,SetFloatField,SetObjectField ......。有Set函式肯定也會有Get函式,與之對應的就是GetIntField(jobject obj, jfieldID fieldID),這個函式是獲取指定屬性的值,引數含義同SetIntField函式
//獲取靜態屬性Id, 第一個引數:類物件, 第二個引數: 屬性名, 第三個引數: 屬性簽名
4.GetStaticFieldID(jclass clazz, const char* name, const char* sig)
//設定靜態屬性的值, 第一個引數: 類物件, 第二個引數: 屬性id, 第三個引數: 要設定的值
5.SetStaticIntField(jclass clazz, jfieldID fieldID, jint value)
//獲取函式id, 第一引數:類物件, 第二個引數:函式名, 第三個引數: 函式簽名(不知道的同學點選這裡)
6.GetMethodID(jclass clazz, const char* name, const char* sig)
//呼叫java中的無返回值函式, 第一個引數: this的意思, 第二個引數: 函式id, 第三個引數:需要傳入的實參
7.CallVoidMethod(jobject obj, jmethodID methodID, ...)
//獲取靜態函式id, 第一個引數: 類物件, 第二個引數: 函式名, 第三個引數: 函式簽名
8.GetStaticMethodID(jclass clazz, const char* name, const char* sig)
//呼叫java中無返回值的靜態函式, 第一個引數: 類物件, 第二個引數: 函式id, 第三個引數: 需要傳入的實參
9.CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
//生成一個jstring型別的方法轉換,該方法會返回一個jstring型別
10.NewStringUTF(const char* bytes)
//呼叫java中的物件型別(String型別被認為物件型別),第一引數:this的意思, 第二個引數:函式Id, 第三個引數:需要傳入的實參
11.CallObjectMethod(jobject, jmethodID, ...);
//獲取類中的物件屬性,第一個引數:this的意思 , 第二個引數:屬性id
12.GetObjectField(jobject obj, jfieldID fieldID)
//根據子類的類物件,獲取父類的類物件, 第一引數:子類類物件
13.GetSuperclass(jclass clazz)
//呼叫java中父類的方法,第一個引數:子類的物件, 第二個引數:父類的類物件, 第三個引數:父類的函式id, 第四個引數:需要傳入的實參
14.CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, ...)
瞭解完這些函式之後我們來看具體使用:
Java_com_liyahong_jni_1field_1method_MainActivity_stringFromJNI(
JNIEnv *env, jobject jobj) {
//修改普通成員變數
jclass jclazz = env->GetObjectClass(jobj);
jfieldID jfieilId = env->GetFieldID(jclazz, "anInt", "I");
jint anInt = 35;
env->SetIntField(jobj, jfieilId, anInt);
//修改靜態成員變數
jfieldID jstaticFieldId = env->GetStaticFieldID(jclazz, "anIntS", "I");
env->SetStaticIntField(jclazz, jstaticFieldId, 55);
//通過jni呼叫普通java方法
jmethodID jmethodId = env->GetMethodID(jclazz, "test", "()V");
env->CallVoidMethod(jobj, jmethodId);
//通過jni呼叫靜態java方法
jmethodID jstaticMethodId = env->GetStaticMethodID(jclazz, "staticTest", "(I)V");
env->CallStaticVoidMethod(jclazz, jstaticMethodId, 55);
//通過jni呼叫java中帶參方法
jmethodID methodId = env->GetMethodID(jclazz, "test", "(ILjava/lang/String;)Ljava/lang/String;");
jstring str = env->NewStringUTF("小麗");
env->CallObjectMethod(jobj, methodId, 18, str);
//通過jni呼叫其他類中的方法
jfieldID sonJfieldId = env->GetFieldID(jclazz, "son", "Lcom/liyahong/jni_field_method/Son;");
jobject sonJobj = env->GetObjectField(jobj, sonJfieldId);
jclass sonClazz = env->GetObjectClass(sonJobj);
jmethodID sonMethodId = env->GetMethodID(sonClazz, "ride", "()V");
env->CallVoidMethod(sonJobj, sonMethodId);
//通過jni呼叫父類中的方法
jclass jfatherClazz = env->GetSuperclass(sonClazz);
jmethodID jfatherMethodId = env->GetMethodID(jfatherClazz, "ride", "()V");
env->CallNonvirtualVoidMethod(sonJobj, jfatherClazz, jfatherMethodId);
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
二、當使用jni呼叫java中的構造方法的時候:
其中的方法名需要寫為<init>,例如:
public Student(){
......
}
這時候不應該寫Student,而是應該寫為:<init>
方便大家理解這裡特意寫了一個例子:
//通過jni例項父類的構造方法
jmethodID fatherMethodId = env->GetMethodID(jfatherClazz, "<init>", "()V");
jobject fatherJobj = env->NewObject(jfatherClazz, fatherMethodId);
//獲取父類中的屬性fatherName
jfieldID fatherFieldId = env->GetFieldID(jfatherClazz, "fatherName", "Ljava/lang/String;");
jstring fatherName = env->NewStringUTF("My name is 張三");
//修改fatherName的值為My name is 張三
env->SetObjectField(fatherJobj, fatherFieldId, fatherName);
jmethodID fatherGetNameId = env->GetMethodID(jfatherClazz, "getFatherName", "()Ljava/lang/String;");
//呼叫父類中的getFatherName方法,獲取屬性值
fatherName = (jstring) env->CallObjectMethod(fatherJobj, fatherGetNameId);
char* resultName = jstringToChar(env, fatherName);
LOGE("%s", resultName);
①這裡涉及到新函式NewObject,其實這個函式很簡單就是一個例項物件的函式,我們知道java中的建構函式都是用來例項物件的,所以顧名思義的,我們要通過jni來呼叫java中的建構函式,就應該呼叫NewObject這個函式,這個函式會返回一個jobject物件,這個物件就是我們例項好的java物件。
②這裡還涉及到了jstringToChar函式,這個函式看名字想必大家都應該知道什麼意思了,這個函式其實就是一個jstring 和 char*的一個型別轉換,原因都不細說了,直接上程式碼:
#include <string.h>
#include <stdlib.h>
// 由於jvm和c++對中文的編碼不一樣,因此需要轉碼。 utf8/16轉換成gb2312
char* jstringToChar(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("UTF-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
三、javap的使用:
1.預熱工作,首先我們要進入classes目錄下使用命令:
cd app/build/intermediates/classes/debug/包名
例如:
cd app/build/intermediates/classes/debug/com/xxx/jni_field_method
下面開始命令:
//獲取MainAtivity下的所有屬性和方法
javap -p MainActivity
//獲取MainAtivity的屬性和方法的簽名
javap -s -p MainActivity
JNI進階二(C++呼叫java陣列 和 JNI引用):傳送門