上一篇看了jni調用靜態方法和修改靜態字段,這一篇學習了jni調用實例方法和修改實例字段
- 調用實例方法,步驟:
- 找到類:利用FindClass,找到類
- 找到要調用的方法id:利用GetMethodID,找到方法id
- 創建實例對象:利用實例對象的構造方法id來創建
- 使用實例對象去調用對應的Method:CallVoidMethod
- 修改實例字段,步驟:
- 找到類:利用FindClass,找到類
- 找到要修改的字段id:利用GetFieldID,找到字段id
- 創建實例對象:利用實例對象的構造方法id來創建
- 設置字段值:SetIntField
有上一篇做基礎,咱們直接來看看:
- 先來一個Java類,後面使用它來創建實例,和使用其中字段與方法
/**
* jni實例調用測試:
* 1. 測試調用打印年齡和姓名,預期打印 0 和null
* 2. 通過修改字段,設置age和name的值,然後再打印
* 3. 重新創建一個對象,初始化方法中傳入性別,調用打印性別的方法
*/
public class ObjectTest {
private static final String TAG = ObjectTest.class.getSimpleName();
/**
* 性別
*/
private String sex;
/**
* 年齡
*/
private int age;
/**
* 姓名
*/
private String name;
/**
* 無參數構造方法
*/
public ObjectTest() {
}
/**
* 帶參數構造方法
*
* @param sex 性別
*/
public ObjectTest(String sex) {
this.sex = sex;
}
/**
* 用於打印age和name
*/
public void logInfo() {
Log.i(TAG, "age:" + age + ",name:" + name);
}
/**
* 用於打印性別
*/
public void logSex() {
Log.i(TAG, "性別:" + sex);
}
}
一. 調用實例方法
(1) 調用無參構造方法創建對象方式
- 找到jclass:依舊是使用全路徑名
jclass class_Objtest = env->FindClass("shixin/ndkdemo/obj_test/ObjectTest");
if (class_Objtest == NULL) {
LOGE("沒找到類");
return;
}
- 找到方法id:一會要調用的logInfo的方法id
jmethodID meth_id_logInfo = env->GetMethodID(class_Objtest, "logInfo", "()V");
- 創建實例
- jni沒有直接方法創建Java對象,那麼就通過使用它構造方法來創建,構造方法的方法名爲:
<init>
- 創建實例使用NewObject方法來創建,具體代碼如下:
/*由於jni沒有直接方法創建java對象,所以通過構造方法來創建*/
//獲取構造方法,這裏使用的是無參構造方法
jmethodID mtheod_construct_init = env->GetMethodID(class_Objtest, "<init>", "()V");
//創建實例Object,NewObject,參數:類、構造方法id
jobject object_ = env->NewObject(class_Objtest, mtheod_construct_init);
if (object_ == NULL) {
LOGE("創建對象失敗");
return;
}
- 調用實例方法:對象也創建好了,那麼就可以調用它的方法了咯,使用CallVoidMethod就行:
//調用方法,CallVoidMethod,參數:實例、要調用的方法id
env->CallVoidMethod(object_, meth_id_logInfo);
就這樣,就能調用對象的方法了,結合上面的Java的logInfo情況,大家應該也能猜到這次的打印是什麼,因爲age和name都是初始值:
ndkdemo I/ObjectTest: age:0,name:null
(2) 調用有參構造方法創建對象方式
調用有參數構造方法來初始化:除了無參數構造方法,也可以調用有參數的構造方法,區別就是在創建對象的時候,比如這裏通過調用對象的有sex參數的構造方法來初始化對象:
- 找到有參數構造方法的id:
//這裏調用sex的構造方法:參數:類、構造方法名、簽名
jmethodID mtheod_construct_init_sex = env->GetMethodID(class_Objtest, "<init>",
"(Ljava/lang/String;)V");
- 創建對象:
//創建一個字符串對象,女
jstring string_sex = env->NewStringUTF("女");
//使用NewObject來創建對象,參數:類、構造方法id、參數值
jobject object_sex = env->NewObject(class_Objtest, mtheod_construct_init_sex, string_sex);
if (object_sex == NULL) {
LOGE("帶參構造方法創建對象失敗");
return;
}
- 找到打印性別的方法id,再調用:
//6.1 找到打印性別的方法id
jmethodID mid_log_sex = env->GetMethodID(class_Objtest, "logSex", "()V");
if (mid_log_sex == NULL) {
LOGE("沒找到打印性別這個方法");
return;
}
//6.2 開始調用
env->CallVoidMethod(object_sex, mid_log_sex);
- 打印:
ndkdemo I/ObjectTest: 性別:女
二. 修改實例字段
前面提到了無參數構造的時候,因爲age和name都是初始值,正好,這裏來給他們賦值一下
- 找到字段id:
//先找到字段的id,參數:類、字段名、字段的簽名,這裏age是int的,所以是I
jfieldID f_id_age = env->GetFieldID(class_Objtest, "age", "I");
if (f_id_age == NULL) {
LOGE("沒找到age字段");
return;
}
//同樣的方式,獲取name的字段id
jfieldID f_id_name = env->GetFieldID(class_Objtest, "name", "Ljava/lang/String;");
if (f_id_name == NULL) {
LOGE("沒找到name字段");
return;
}
- 給age和name字段賦值:
這裏的age是12,name是"小寶貝"
//設置age值,參數:實例對象、字段id、值
env->SetIntField(object_, f_id_age, age);
//因爲String是Object類型,所以使用SetObjectField
env->SetObjectField(object_, f_id_name, name_);
- 再試試打印age和name方法:
meth_id_logInfo是之前的logInfo方法id
//修改了值之後,再來驗證調用一次logInfo,查看打印
env->CallVoidMethod(object_, meth_id_logInfo);
- 打印:
ndkdemo I/ObjectTest: age:12,name:小寶貝
看打印,這就能看到,咱們是賦值成功了
三. 整體代碼
- java中的native方法,與調用傳值:
//測試調用事例方法
testObjectMethodUse(12,"小寶貝");
/**
* 實例方法調用
*/
private native void testObjectMethodUse(int age, String name);
- jni完整實現:
/**
* 實例方法調用測試
*/
extern "C"
JNIEXPORT void JNICALL
Java_shixin_ndkdemo_MainActivity_testObjectMethodUse(JNIEnv *env, jobject instance, jint age,
jstring name_) {
//1. 利用FindClass,找到jclass
jclass class_Objtest = env->FindClass("shixin/ndkdemo/obj_test/ObjectTest");
if (class_Objtest == NULL) {
LOGE("沒找到類");
return;
}
//2. 利用GetMethodID,找到方法id
jmethodID meth_id_logInfo = env->GetMethodID(class_Objtest, "logInfo", "()V");
/*3. 創建實例對象,由於jni沒有直接方法創建java對象,所以通過構造方法來創建*/
//3.1 獲取構造方法,這裏使用的是無參構造方法
jmethodID mtheod_construct_init = env->GetMethodID(class_Objtest, "<init>", "()V");
//3.2 創建實例Object,NewObject,參數:類、構造方法id
jobject object_ = env->NewObject(class_Objtest, mtheod_construct_init);
if (object_ == NULL) {
LOGE("創建對象失敗");
return;
}
//4. 調用方法,CallVoidMethod,參數:實例、要調用的方法id
env->CallVoidMethod(object_, meth_id_logInfo);
/*5. 試試修改字段.這裏我們利用java調用傳進來的age和name值來修改ObjectTest中的age和name*/
//5.1 先找到字段的id,參數:類、字段名、字段的簽名,這裏age是int的,所以是I
jfieldID f_id_age = env->GetFieldID(class_Objtest, "age", "I");
if (f_id_age == NULL) {
LOGE("沒找到age字段");
return;
}
//5.2 設置age值,參數:實例對象、字段id、值
env->SetIntField(object_, f_id_age, age);
//5.3 同樣的方式,獲取name的字段id和設置值
jfieldID f_id_name = env->GetFieldID(class_Objtest, "name", "Ljava/lang/String;");
if (f_id_name == NULL) {
LOGE("沒找到name字段");
return;
}
//因爲String是Object類型,所以使用SetObjectField
env->SetObjectField(object_, f_id_name, name_);
//5.4 修改了值之後,再來驗證調用一次logInfo,查看打印
env->CallVoidMethod(object_, meth_id_logInfo);
/*6. 測試下帶參數的構造方法,傳入性別,再調用打印性別的方法*/
jmethodID mtheod_construct_init_sex = env->GetMethodID(class_Objtest, "<init>",
"(Ljava/lang/String;)V");
jstring string_sex = env->NewStringUTF("女");
jobject object_sex = env->NewObject(class_Objtest, mtheod_construct_init_sex, string_sex);
if (object_sex == NULL) {
LOGE("帶參構造方法創建對象失敗");
return;
}
//6.1 找到打印性別的方法id
jmethodID mid_log_sex = env->GetMethodID(class_Objtest, "logSex", "()V");
if (mid_log_sex == NULL) {
LOGE("沒找到打印性別這個方法");
return;
}
//6.2 開始調用
env->CallVoidMethod(object_sex, mid_log_sex);
/*釋放類和對象*/
env->DeleteLocalRef(class_Objtest);
env->DeleteLocalRef(object_);
env->DeleteLocalRef(string_sex);
env->DeleteLocalRef(object_sex);
}
- 輸出:
ndkdemo I/ObjectTest: age:0,name:null
ndkdemo I/ObjectTest: age:12,name:小寶貝
ndkdemo I/ObjectTest: 性別:女
四. 總結
- 調用實例方法和修改字段與靜態的挺類似,區別就在於要創建實例,使用創建的實例去訪問其中的方法和字段,而靜態的則只需要類,這個我想不用過多解釋,Java特性大家都應該瞭解
- 調用方法:CallXXMethod。其中XX代表返回值,Void代表無返回,Int代表返回Int
- 修改字段:SetXXField。其中XX代表字段類型,Int代表int類型,Object代表對象類型。
- 另外jni去調用實例方法、字段的時候,java中的private這種限定符就沒意義了,private的字段jni中一樣可以直接修改,或許jni的方式就是認爲內部調用的吧。還好,final字段的修改不會成功,不然顛覆了,哈哈