1. 程式人生 > >JNI進階一 (C++呼叫java屬性和方法,javap的使用)

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引用):傳送門