人間觀察
1024-程式員節
愿各位程式員歷盡千帆,歸來仍是少年,
這片文章本來不打算寫的,因為在前面的文章多多少少的提到了jni和java的互動,但是為了讓知識體系更健全寫,還是梳理下,算是jni和java的在互動上的一個總結吧,
兩者的互動歸納起來主要就是兩種,
- java呼叫jni,比如:傳遞基本資料,復雜物件等
- jni呼叫java,比如回呼,例外,呼叫java方法/成員變數,構造java物件等等
java呼叫jni-傳到復雜物件到jni中
我們新建一個java的物件,然后傳遞到jni中,在jni中獲取該物件的屬性值,
java物件如下
package com.bj.gxz.jniapp.methodfield;
import java.io.Serializable;
/**
* Created by guxiuzhong on 2020/10/24.
*/
public class AppInfo implements Serializable {
private static final String TAG = "AppInfo";
private String versionName;
public int versionCode;
public long size;
public AppInfo(String versionName) {
this.versionName = versionName;
}
public AppInfo(String versionName, int versionCode) {
this.versionName = versionName;
this.versionCode = versionCode;
}
public String getVersionName() {
return versionName;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
public int getVersionCode() {
return versionCode;
}
public void setVersionCode(int versionCode) {
this.versionCode = versionCode;
}
public void setSize(long size) {
this.size = size;
}
public long getSize() {
return size;
}
@Override
public String toString() {
return "AppInfo{" +
"versionName='" + versionName + '\'' +
", versionCode=" + versionCode +
", size=" + size +
'}';
}
}
jni介面為
public native void getAppInfoFromJava(AppInfo appInfo);
對應jni的方法是
extern "C" JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_methodfield_JNIMethodField_getAppInfoFromJava(JNIEnv *env, jobject instance,
jobject obj) {
// ...
}
最后一個引數obj就是對應java傳過來的物件AppInfo, 因為在jni中所有的java物件都是jobject,對應關系,這里再貼一下:
基本資料型別:
java與Native映射關系如下表所示:
| Java型別 | Native 型別 | Description |
|---|---|---|
| boolean | jboolean | unsigned 8 bits |
| byte | jbyte | signed 8 bits |
| char | jchar | unsigned 16 bits |
| short | jshort | signed 16 bits |
| int | jint signed | 32 bits |
| long jlong | signed | 64 bits |
| float | jfloat | 32 bits |
| double | jdouble | 64 bits |
| void | void | not applicable |
參考資料型別
外面的為jni中的,括號中的java中的,
- jobject
- jclass (java.lang.Class objects)
- jstring (java.lang.String objects)
- jarray (arrays)
- jobjectArray (object arrays)
- jbooleanArray (boolean arrays)
- jbyteArray (byte arrays)
- jcharArray (char arrays)
- jshortArray (short arrays)
- jintArray (int arrays)
- jlongArray (long arrays)
- jfloatArray (float arrays)
- jdoubleArray (double arrays)
- jthrowable (java.lang.Throwable objects)
上面的層次中的jni的參考型別代表了繼承關系,jbooleanArray繼承jarray,jarray繼承jobject,最終都繼承jobject,
ok,
jni呼叫java物件的方法
呼叫物件的某個方法 Call<回傳型別>Method<傳參型別>,比如呼叫AppInfo的getVersionCode對應的就是CallIntMethod,呼叫setVersionCode對應的就是CallVoidMethod方法,
| Call<回傳型別>Method<傳參型別> | Native 型別 | java型別 |
|---|---|---|
| CallVoidMethod() CallVoidMethodA() CallVoidMethodV() | void | void |
| CallObjectMethod() CallObjectMethodA() CallObjectMethodV() | jobject | object |
| CallBooleanMethod() CallBooleanMethodA() CallBooleanMethodV() | jboolean | boolean |
| CallByteMethod() CallByteMethodA() CallByteMethodV() | jbyte | byte |
| CallCharMethod() CallCharMethodA() CallCharMethodV() | jchar | char |
| CallShortMethod() CallShortMethodA() CallShortMethodV() | jshort | short |
| CallIntMethod() CallIntMethodA() CallIntMethodV() | jint | int |
| CallLongMethod() CallLongMethodA() CallLongMethodV() | jlong | long |
| CallFloatMethod() CallFloatMethod A() CallFloatMethodV() | jlong | long |
| CallDoubleMethod() CallDoubleMethodA() CallDoubleMethodV() | jfloat | float |
| 如果java方法是靜態的如下 | - | - |
| CallStaticShortMethod() CallStaticShortMethodA() CallStaticShortMethodV() | jshort | short |
| 省略其它方法… | - | - |
具體可以參考官網:官網開發檔案
每一種回傳型別對應3個方法,唯一不同的是輸入引數的傳入形式不同,所以getVersionName對應的就是CallObjectMethod,
CallObjectMethod引數:
obj:某個 Java 物件實體
methodID:指定方法的ID
args:輸入引數串列,方法如果沒有引數則不寫
特別注意
如果你呼叫的是Java物件的方法CallxxxxMethod第一個引數是某個 Java 物件實體,但是如果你呼叫的是靜態方法,則第一個引數是jclass,靜態方法并不屬于某一個物件,
methodID的獲取通過GetMethodID,在jni.h頭檔案中函式宣告原型為:
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
GetMethodID引數
clazz: 對應java的class類; 為java類的全類名比如把點改為/即com.bj.gxz.jniapp.methodfield.AppInfo改為com/bj/gxz/jniapp/methodfield/AppInfo
const char* name 方法名字;
const char* sig 方法的簽名,方法的簽名可以通過javap -s xxx.class獲取,
示例獲取getVersionName的完整代碼如下:
// 根據java物件獲取物件對應的class
jclass cls = env->GetObjectClass(obj);
// 獲取&呼叫java方法
jmethodID getVersionName_mid = env->GetMethodID(cls, "getVersionName", "()Ljava/lang/String;");
jstring versionName = (jstring) env->CallObjectMethod(obj, getVersionName_mid);
char *c_string = const_cast<char *>(env->GetStringUTFChars(versionName, 0));
LOG_D("versionName=%s", c_string);
看著很簡單吧,
jni獲取java物件的屬性值
獲取java物件的屬性對應的值的方法為GetXXXXField,XXXX 為資料型別,比如GetIntField,GetShortField等等,如果不是基本型則為GetObjectField,如果屬性為static的則加上Static,比如GetStaticIntField,GetStaticObjectField,在jni中是不看成員變數的作用域的,不管你是private,protected,public的,加finnal也一樣,它都可以讀取里面的值,和反射不一樣,
GetXXXXField引數
obj:某個 Java 物件實體
jfieldID:指定屬性的ID
GetFieldID引數
clazz:物件java物件的class
const char* name:屬性名字
const char* sig:資料型別描述符
資料型別描述符java和jni的對應關系如下,來:
| Java型別 | 型別描述符 |
|---|---|
| int | I |
| long | J |
| byte | B |
| short | S |
| char | C |
| float | F |
| double | D |
| boolean | Z |
| void | V |
| 陣列 | [ |
| 二維陣列 | [[ |
| 其他參考型別 | L+類全名+; |
例子如下:
// 獲取java物件的屬性值
jfieldID versionCode_fieldId = env->GetFieldID(cls, "versionCode", "I");
int versionCode = env->GetIntField(obj, versionCode_fieldId);
LOG_D("versionCode=%d", versionCode);
// 獲取java物件的屬性值
jfieldID size_fieldId = env->GetFieldID(cls, "size", "J");
long size = env->GetLongField(obj, size_fieldId);
LOG_D("size=%ld", size);
// 獲取java靜態屬性的值
jfieldID tag_fieldId = env->GetStaticFieldID(cls, "TAG", "Ljava/lang/String;");
jstring tag_java = (jstring) env->GetStaticObjectField(cls, tag_fieldId);
char *tag_c_string = const_cast<char *>(env->GetStringUTFChars(tag_java, 0));
LOG_D("TAG=%s", tag_c_string);
運行后:
JNIMethodField jniMethodField = new JNIMethodField();
AppInfo javaInfo = new AppInfo("com.wg.com", 30);
javaInfo.setSize(500);
jniMethodField.getAppInfoFromJava(javaInfo);
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: versionName=com.wg.com
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: versionCode=30
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: size=500
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp D/JNI: TAG=AppInfo
屬性的獲取和方法的獲取都差不多,
jni中構造java物件&呼叫java方法
關于的回呼例外可以參考前面的文章,
Android-JNI開發系列《二》-在jni層的執行緒中回呼到java層
Android-JNI開發系列《三》-例外處理
我們在jni中創建一個java物件,其實就是呼叫java的構造方法,只不過是構造方法的函式簽名為固定值<init>. 在jni中創建復雜物件(任何java物件)用NewObject方法,同樣有不同引數的NewObjectV,NewObjectA
jni.h函式宣告原型為:
jobject NewObject(jclass clazz, jmethodID methodID, ...)
引數
clazz java類的class;
methodID 指定方法的ID;
… 引數 支持多個引數;
clazz和methodID同上文方法中的介紹,
示例如下:
// 獲取java的class
jclass cls = env->FindClass("com/bj/gxz/jniapp/methodfield/AppInfo");
// 創建java物件,就是呼叫構造方法,構造方法的方法簽名固定為<init>
jmethodID mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
jobject obj = env->NewObject(cls, mid, env->NewStringUTF("com.gxz.com"));
// 給定方法名字和簽名,呼叫方法
jmethodID setVersionCode_mid = env->GetMethodID(cls, "setVersionCode", "(I)V");
env->CallVoidMethod(obj, setVersionCode_mid, 1);
// 給定屬性名字和簽名,設定屬性的值
jfieldID size_field_id = env->GetFieldID(cls, "size", "J");
env->SetLongField(obj, size_field_id, (jlong) 1000);
size的屬性我們是通過SetLongField設定的,見名知義,這個和獲取屬性一樣/呼叫java中的方法一樣,它也有類似SetLongField,SetIntField,SetStaticIntField,SetObjectField等等,區分了基本型別,靜態屬性,參考型別,大家可以嘗試一下,這里不多介紹了,
運行后:
AppInfo info = jniMethodField.createAppInfoFromJni();
Log.e(TAG, "info=" + info);
輸出
10-23 23:25:27.572 2900-2900/com.bj.gxz.jniapp E/JNI: info=AppInfo{versionName='com.gxz.com', versionCode=1, size=1000}
最后源代碼:https://github.com/ta893115871/JNIAPP
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/192756.html
標籤:其他
