人間觀察
1024-程序員節
願各位程序員歷盡千帆,歸來還是少年。
html
這片文章原本不打算寫的,由於在前面的文章多多少少的提到了jni和java的交互,可是爲了讓知識體系更健全寫,仍是梳理下,算是jni和java的在交互上的一個總結吧。
二者的交互概括起來主要就是兩種。
java
- java調用jni。好比:傳遞基本數據,複雜對象等
- jni調用java。好比回調,異常,調用java方法/成員變量,構造java對象等等
java調用jni-傳到複雜對象到jni中
咱們新建一個java的對象,而後傳遞到jni中,在jni中獲取該對象的屬性值。git
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接口爲github
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
。對應關係,這裏再貼一下:oracle
基本數據類型:app
java與Native映射關係以下表所示:ide
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層
咱們在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}