Android NDK JNI 入門筆記-day03-引用數據類型

* Android NDK JNI 入門筆記目錄 *

Java & JNI 引用數據類型

對應於不同類型的 Java 對象, JNI 包含大量的引用類型

java-jni-reference-types

Java 的類類型 JNI 的引用類型 類型描述
java.lang.Object jobject 可以表示任何 Java 的對象,或者沒有 JNI 對應類型的 Java 對象(實例方法的強制參數)
java.lang.String jstring Java 的 String 字符串類型的對象
java.lang.Class jclass Java 的 Class 類型對象(靜態方法的強制參數)
Object[] jobjectArray Java 任何對象的數組表示形式
boolean[] jbooleanArray Java 基本類型 boolean 的數組表示形式
byte[] jbyteArray Java 基本類型 byte 的數組表示形式
char[] jcharArray Java 基本類型 char 的數組表示形式
short[] jshortArray Java 基本類型 short 的數組表示形式
int[] jintArray Java 基本類型 int 的數組表示形式
long[] jlongArray Java 基本類型 long 的數組表示形式
float[] jfloatArray Java 基本類型 float 的數組表示形式
double[] jdoubleArray Java 基本類型 double 的數組表示形式
java.lang.Throwable jthrowable Java 的 Throwable 類型,表示異常的所有類型和子類

JNI 引用類型-Java 基本數據類型數組

寫幾個示例感受一下 JNI 對數組的操作。

day03-array

從 Native 獲取數組,Java 進行格式化

/**
 * 從 Native 獲取數組,Java 進行格式化
 * @param view
 */
public void getNativeArray(View view) {
    boolean[] nativeArray = NativeUtil.getNativeArray();
    StringBuilder stringBuilder = new StringBuilder("[");
    for(int i = 0; i < nativeArray.length; i++) {
        stringBuilder.append(nativeArray[i]);
        if(i != nativeArray.length -1) {
            stringBuilder.append(", ");
        }
    }
    stringBuilder.append("]");
    getNativeArrayText.setText(stringBuilder.toString());
}

// JNI 對數組的操作
extern "C"
JNIEXPORT jbooleanArray JNICALL
Java_com_ihubin_ndkjni_NativeUtil_getNativeArray(JNIEnv *env, jclass clazz) {
    jboolean* jb = new jboolean[5];
    jb[0] = JNI_TRUE;
    jb[1] = JNI_FALSE;
    jb[2] = JNI_TRUE;
    jb[3] = JNI_FALSE;
    jb[4] = JNI_TRUE;

    jbooleanArray jba = env->NewBooleanArray(5);
    env->SetBooleanArrayRegion(jba, 0, 5, jb);

    return jba;
}

將 Java 數組傳入 Native,Native 格式化

/**
 * 將 Java 數組傳入 Native,Native 格式化
 * @param view
 */
public void formatArray(View view) {
    int[] intArray = {11, 22, 33, 44, 55};
    String formatArrayStr = NativeUtil.formatArray(intArray);
    formatArrayText.setText(formatArrayStr);
}

// JNI 對數組的操作
Java_com_ihubin_ndkjni_NativeUtil_formatArray(JNIEnv *env, jclass clazz, jintArray int_array) {
    jint array[5];
    env->GetIntArrayRegion(int_array, 0, 5, array);
    jsize size = env->GetArrayLength(int_array);
    char resutStr[100] = {0};
    char str[10] = {0};
    strcat(resutStr, "[");
    for(int i = 0; i < size; i++) {
        sprintf(str, "%d", array[i]);
        strcat(resutStr, str);
        if(i != size - 1) {
            strcat(resutStr, ", ");
        }
    }
    strcat(resutStr, "]");
    return env->NewStringUTF(resutStr);
}

Native 計算商品總價

/**
 * Native 計算商品總價
 * @param view
 */
public void calcTotalMoney(View view) {
    double[] price = {5.5, 6.6, 7.7, 8.8, 9.9};
    String resultStr = NativeUtil.calcTotalMoney(price);
    calcTotalMoneyText.setText(resultStr);
}

// JNI 對數組的操作
extern "C"
JNIEXPORT jstring JNICALL
Java_com_ihubin_ndkjni_NativeUtil_calcTotalMoney(JNIEnv *env, jclass clazz, jdoubleArray price) {
    jdouble array[5];
    env->GetDoubleArrayRegion(price, 0, 5, array);
    jsize size = env->GetArrayLength(price);

    char resutStr[255] = {0};
    char str[20] = {0};
    strcat(resutStr, "sum(");
    jdouble totalMoney = 0.0;
    for(int i = 0; i < size; i++) {
        sprintf(str, "%.1f", array[i]);
        strcat(resutStr, str);
        if(i != size - 1) {
            strcat(resutStr, ", ");
        }
        totalMoney += array[i];
    }
    strcat(resutStr, ")");
    strcat(resutStr, "\n=");
    sprintf(str, "%.1f", totalMoney);
    strcat(resutStr, str);
    return env->NewStringUTF(resutStr);
}

Native 計算各科成績是否通過

/**
 * 計算各科成績是否通過
 * @param view
 */
public void calcScorePass(View view) {
    float[] yourScore = {59.0F, 88.0F, 76.5F, 45.0F, 98.0F};
    String[] yourScoreResult = NativeUtil.calcScorePass(yourScore);
    StringBuilder stringBuilder = new StringBuilder();

    stringBuilder.append("[");
    for(int i = 0; i < yourScore.length; i++) {
        stringBuilder.append(yourScore[i]);
        if(i != yourScore.length - 1) {
            stringBuilder.append(", ");
        }
    }
    stringBuilder.append("]");

    stringBuilder.append("\n");

    stringBuilder.append("[");
    for(int i = 0; i < yourScoreResult.length; i++) {
        stringBuilder.append(yourScoreResult[i]);
        if(i != yourScoreResult.length - 1) {
            stringBuilder.append(", ");
        }
    }
    stringBuilder.append("]");

    calcScorePassText.setText(stringBuilder.toString());
}


extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_ihubin_ndkjni_NativeUtil_calcScorePass(JNIEnv *env, jclass clazz, jfloatArray your_score) {
    jfloat array[5];
    env->GetFloatArrayRegion(your_score, 0, 5, array);
    jsize size = env->GetArrayLength(your_score);

    jclass objClass = env->FindClass("java/lang/String");
    jobjectArray objArray = env->NewObjectArray(5, objClass, 0);
    jstring  jstr;
    for(int i = 0; i < size; i++) {
        if(array[i] >= 60.0) {
            jstr = env->NewStringUTF("√");
        } else {
            jstr = env->NewStringUTF("×");
        }
        env->SetObjectArrayElement(objArray, i, jstr);
    }

    return objArray;
}

查看一下結果:

day03-array-result

到這裏,我們已經‘熟練掌握’了 JNI 對 Java 基本數據類型數組的各種操作。

Native 計算各科成績是否通過 這個示例中,出現了一段代碼 jclass objClass = env->FindClass("java/lang/String"); 下面就來探索一下。

JNI 引用類型-Java 對象

之前,都是在 Java 調用 Native 中的方法,現在,Native 代碼反調用 Java 層代碼。

Class/屬性和方法/對象

一、獲取 Class 對象

爲了能夠在 C/C++ 中調用 Java 中的類,jni.h 的頭文件專門定義了 jclass 類型表示 Java 中 Class 類。JNIEnv 中有 3 個函數可以獲取 jclass。

// 通過類的名稱(類的全名,這時候包名不是用'"."點號而是用"/"來區分的)來獲取 jclass
jclass FindClass(const char* clsName)

// 通過對象實例來獲取 jclass,相當於 Java 中的 getClass() 函數
jclass GetObjectClass(jobject obj)

// 通過 jclass 可以獲取其父類的 jclass 對象
jclass getSuperClass(jclass obj)

例:

// 獲取 Java 中的類 java.lang.String
jclass objClass = env->FindClass("java/lang/String");

// 獲取一個類的父類
jclass superClass = env->getSuperClass(objClass);

二、獲取屬性方法

在 C/C++ 獲取 Java 層的屬性和方法,JNI 在 jni.h 頭文件中定義了 jfieldID 和 jmethodID 這兩種類型來分別代表 Java 端的屬性和方法。

// 獲取屬性
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)

// 獲取方法
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig)

// 獲取靜態屬性
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)

// 獲取靜態方法
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig)

例:

package com.ihubin.ndkjni;

public class User {

    public static int staticField = 88;

    public int normalField = 99;

    public static String getStaticUserInfo() {
        return "[name:hubin, age:18]";
    }
    
    public String getNormalUserInfo() {
        return "[name:hubin, age:28]";
    }    
    
     private String name;

    private int age;

    public User() {}

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getFormatInfo() {
        return String.format("[name:%s, age:%d]", name, age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
}


// 獲取 jclass
jclass userClass = env->FindClass("com/ihubin/ndkjni/User");

// 獲取屬性 ID
jfieldID normalField = env->GetFieldID(userClass, "normalField", "I");
// 獲取靜態屬性 ID
jfieldID staticField = env->GetStaticFieldID(userClass, "staticField", "I");

// 獲取方法 ID
jmethodID normalMethod = env->GetMethodID(userClass, "getNormalUserInfo", "()Ljava/lang/String;");
// 獲取靜態方法 ID
jmethodID staticMethod = env->GetStaticMethodID(userClass, "getStaticUserInfo", "()Ljava/lang/String;");

// 獲取無參構造函數
jmethodID voidInitMethod = env->GetMethodID(userClass, "<init>", "()V");
// 獲取有參構造函數
jmethodID paramInitMethod = env->GetMethodID(userClass, "<init>", "(Ljava/lang/String;I)V");

三、構造對象

類實例化以後才能訪問裏面的非靜態屬性、方法,下面通過上面獲取到的 jclass 和構造函數 jmethodID 構造對象。

// 通過 clazz/methodID/...(可變參數列表) 創建一個對象
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

// args(參數數組)
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);

// args(指向變參列表的指針)
jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);

// 通過一個類創建一個對象,默認構造函數
jobject AllocObject(JNIEnv *env, jclass clazz)

例:

// 通過無參構造函數
jobject userOne = env->NewObject(userClass, voidInitMethod);

// 通過有參構造函數
jstring name = env->NewStringUTF("HUBIN");
jint age = 8;
jobject userTwo = env->NewObject(userClass, paramInitMethod, name, age);

// 默認構造函數
jobject userThree = env->AllocObject(userClass);

四、獲取屬性、調用方法

之前的準備都是爲了能使用 Java 對象中的屬性、方法。

獲取、設置屬性值:
XXX Get<type>Field(jobject obj, jfieldID fieldID)
Set<type>Field(jobject obj, jfieldID fieldID, XXX value)

獲取、設置靜態屬性值:
XXX GetStatic<type>Field(jclass clazz, jfieldID fieldID)
SetStatic<type>Field(jclass clazz, jfieldID fieldID, XXX value)

調用方法:
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...)
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args)
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args)

調用靜態方法:
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...)
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args)
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args)

例:

// 獲取對象中的屬性
jint normalFieldValue = env->GetIntField(userOne, normalField);
LOGD("normalField: %d", normalFieldValue);

// 獲取 class 中靜態屬性
jint staticFieldValue = env->GetStaticIntField(userClass, staticField);
LOGD("staticField: %d", staticFieldValue);

// 調用對象中的方法
jobject normalMethodResultObj = env->CallObjectMethod(userOne, normalMethod);
jstring normalMethodResult = static_cast<jstring>(normalMethodResultObj);
const char *normalMethodResultNativeString = env->GetStringUTFChars(normalMethodResult, 0);
LOGD("normalMethodResult: %s", normalMethodResultNativeString);

// 調用 class 中的靜態方法
jobject staticMethodResultObj = env->CallStaticObjectMethod(userClass, staticMethod);
jstring staticMethodResult = static_cast<jstring>(staticMethodResultObj);
const char *staticMethodResultNativeString = env->GetStringUTFChars(staticMethodResult, 0);
LOGD("staticMethodResult: %s", staticMethodResultNativeString);

// 調用對象中的方法
jobject getFormatInfoMethodResultObj = env->CallObjectMethod(userTwo, getFormatInfoMethod);
jstring getFormatInfoMethodResult = static_cast<jstring>(getFormatInfoMethodResultObj);
const char *getFormatInfoMethodResultNativeString = env->GetStringUTFChars(getFormatInfoMethodResult, 0);
LOGD("getFormatInfoMethodResult: %s", getFormatInfoMethodResultNativeString);

// 測試 jobject AllocObject(JNIEnv *env, jclass clazz) 創建的對象
jobject userThreeMethodResultObj = env->CallObjectMethod(userThree, normalMethod);
jstring userThreeMethodResult = static_cast<jstring>(userThreeMethodResultObj);
const char *userThreeMethodResultNativeString = env->GetStringUTFChars(userThreeMethodResult, 0);
LOGD("userThreeMethodResult: %s", userThreeMethodResultNativeString);

day03-example-result

在獲取 jfieldID 、 jmethodID 時,出現了一些奇怪的字符串 I ()Ljava/lang/String; ()V (Ljava/lang/String;I)V,下面就來研究一下。

數據類型簽名

在 JVM 虛擬機中,存儲數據類型的名稱時,是使用指定的類型簽名來存儲,而不是我們習慣的 int,float 等。
JNI 使用的就是這種類型簽名。

Java 類型 類型簽名
int I
long J
byte B
short S
char C
float F
double D
boolean Z
void V
其他引用類型 L+類全名+;
數組 [
方法 (參數)返回值

例子1

Java 類型:java.lang.String
類型簽名:Ljava/lang/String;
即一個 Java 類對應的簽名,就是 L 加上類的全名,其中 . 要換成 / ,最後不要忘掉末尾的分號。

例子2

Java 類型:String[]
類型簽名:[Ljava/lang/String;

Java 類型:int[][]
類型簽名:[[I

數組就是簡單的在類型描述符前加 [ 即可,二維數組就是兩個 [ ,以此類推。

例子3

Java 方法:long f (int n, String s, int[] arr);
類型簽名:(ILjava/lang/String;[I)J

Java 方法:void f ();
類型簽名:()V

括號內是每個參數的類型符,括號外就是返回值的類型符。

使用 javap -s <Class> 查看簽名

javap-get-method-signature

至此,我們已經學會了在 Android 項目中 Native 操作 Java 對象。


代碼:

NDKJNIday03

參考資料:

Oracle - JNI Types and Data Structures

JNI基礎:JNI數據類型和類型描述符

Android JNI學習(三)——Java與Native相互調用

JNI完全指南(四)——對象操作


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章