人間觀察
1024-程式設計師節
願各位程式設計師歷盡千帆,歸來仍是少年。
這片文章本來不打算寫的,因為在前面的文章多多少少的提到了jni和java的互動,但是為了讓知識體系更健全寫,還是梳理下,算是jni和java的在互動上的一個總結吧。
兩者的互動歸納起來主要就是兩種。
我們新建一個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中的。
上面的層次中的jni的參照型別代表了繼承關係,jbooleanArray繼承jarray,jarray繼承jobject,最終都繼承jobject。
ok。
呼叫物件的某個方法 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);
看著很簡單吧。
獲取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
屬性的獲取和方法的獲取都差不多。
關於的回撥異常可以參考前面的文章。
Android-JNI開發系列《二》-在jni層的執行緒中回撥到java層
我們在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}