Android-JNI開發系列《六》jni與java的交互

人間觀察
1024-程序員節
願各位程序員歷盡千帆,歸來還是少年。

html

這片文章原本不打算寫的,由於在前面的文章多多少少的提到了jni和java的交互,可是爲了讓知識體系更健全寫,仍是梳理下,算是jni和java的在交互上的一個總結吧。
二者的交互概括起來主要就是兩種。
java

  1. java調用jni。好比:傳遞基本數據,複雜對象等
  2. 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<傳參類型>,好比調用AppInfogetVersionCode對應的就是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 爲數據類型,好比GetIntFieldGetShortField等等,若是不是基本型則爲GetObjectField,若是屬性爲static的則加上Static,好比GetStaticIntFieldGetStaticObjectField。在jni中是不當作員變量的做用域的,無論你是privateprotectedpublic的,加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方法,一樣有不一樣參數的NewObjectVNewObjectA

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中的方法同樣,它也有相似SetLongFieldSetIntField,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

相關文章
相關標籤/搜索