Android NDK(二)- JNI 基礎(chǔ)

JNI 類型

JNI 中有許多和 Java 相對應(yīng)的類型

Java 類型 JNI 類型
boolean jboolean
byte jbyte
char jchar
short jshort
int jint
long jlong
float jfloat
double jdouble
void void
java.lang.Class jclass
java.lang.String jstring
java.lang.Throwable jthrowable
object jobject
object[] jobjectarray
boolean[] jbooleanarray
byte[] jbytearray
char[] jchararray
short[] jshortarray
int[] jintarray
long[] jlongarray
float[] jfloatarray
double[] jdoublearray

關(guān)于 JNI 中基本數(shù)據(jù)類型和 C++ 中的基本數(shù)據(jù)類型,它們是可以直接互相轉(zhuǎn)化的,不需要特別的操作。參照 jni.h 文件可以發(fā)現(xiàn),其實就是換了個名字而已,其實本質(zhì)是一個東西。

/* Primitive types that match up with Java equivalents. */
typedef uint8_t  jboolean; /* unsigned 8 bits */
typedef int8_t   jbyte;    /* signed 8 bits */
typedef uint16_t jchar;    /* unsigned 16 bits */
typedef int16_t  jshort;   /* signed 16 bits */
typedef int32_t  jint;     /* signed 32 bits */
typedef int64_t  jlong;    /* signed 64 bits */
typedef float    jfloat;   /* 32-bit IEEE 754 */
typedef double   jdouble;  /* 64-bit IEEE 754 */

/* "cardinal indices and sizes" */
typedef jint     jsize;

類型簽名

Java 類型 JNI 類型簽名
void V
boolean Z
byte B
char C
short S
int I
long J
float F
double D
L fully-qualified-class ; fully-qualified-class
type[] [ type
method type ( arg-types ) ret-type

例如,下面的 Java 方法:
public long foo(int n, String s, int[] arr)
在 JNI 中的簽名如下:
(ILjava/lang/String;[I)J

JNI 引用

JNI 定義了八種 Java 基本類型,其余的 jobject、jclass、jarray、jxxxArray、jstring 等都是引用類型。

JNI 的引用有兩層含義:

  1. Java 中的引用類型
  2. C/C++ 中的指針

但是如果引用被 JVM 釋放了,指針仍然指向一個地址,只是對應(yīng)的地址中數(shù)據(jù)已經(jīng)被釋放了

JNI 的引用分為四種:

  1. 全局引用(GlobalReferences):全局有效。JVM 無法釋放回收,必須通過調(diào)用 DeleteGlobalRef() 顯式釋放。
    創(chuàng)建全局引用:jobject NewGlobalRef(JNIEnv *env, jobject obj);
    釋放全局引用:void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

  2. 弱全局引用(WeakGlobalReferences):一種特殊的全局引用,可以被 JVM 回收。
    創(chuàng)建弱全局引用:jobject NewWeakGlobalRef(JNIEnv *env, jobject obj);
    釋放弱全局引用:void DeleteWeakGlobalRef(JNIEnv *env, jobject globalRef);

  3. 局部引用(LocalReferences):在方法內(nèi)創(chuàng)建,方法結(jié)束后自動釋放。雖然會在方法結(jié)束后自動釋放,但是如果消耗過多 JVM 資源,也可以手動釋放。
    創(chuàng)建局部引用:jobject NewLocalRef(JNIEnv *env, jobject obj);
    釋放局部引用:void DeleteLocalRef(JNIEnv *env, jobject globalRef);

    雖然方法結(jié)束會自動釋放,但是建議使用完了就手動釋放。尤其以下兩種情況必須手動釋放:

    1. 引用一個很大的 Java 對象
    2. 在 for 循環(huán)中創(chuàng)建了大量的引用。引用多了之后會報 ReferenceTable overflow 異常。

    哪些場景需要釋放?JNI 函數(shù)內(nèi)部創(chuàng)建的 jobject、jclass、jstring、jarray 等引用都需要釋放。

    • FindClass / DeleteLocalRef
    • NewString / DeleteLocalRef
    • NewStringUTF / DeleteLocalRef
    • NewObject / DeleteLocalRef
    • NewXxxArray / DeleteLocalRef
    • GetObjectField / DeleteLocalRef
    • GetObjectClass / DeleteLocalRef
    • GetObjectArrayElement / DeleteLocalRef
    • 注意:對于 GetStringChars、GetStringUTFChars、GetXxxArrayElements 基本類型數(shù)組,需要調(diào)用對應(yīng)的 Release 方法去釋放本地內(nèi)存

  4. 無效引用(InvalidReferences):無效引用一般情況下沒有什么用,不展開介紹。

字段和方法 ID

jfieldID 和 jmethodID 是常規(guī)的 C 指針類型,它們的聲明如下:

struct _jfieldID;              /* opaque structure */
typedef struct _jfieldID *jfieldID;   /* field IDs */

struct _jmethodID;              /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method IDs */

GetFieldID / GetXxxField / SetXxxField
GetStaticFieldID / GetStaticXxxField / SetStaticXxxField

/*
 * @param env: JN I接口指針。
 * @param clazz:一個 Java 類。
 * @param name:字段名稱,以 \0 結(jié)尾的 UTF-8 字符串。
 * @param sig:字段簽名,以 \0 結(jié)尾的 UTF-8 字符串。
 * @return 返回字段 ID,如果操作失敗返回 NULL。
 */
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
// 獲取靜態(tài)字段
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);


jobject     GetObjectField(JNIEnv*, jobject, jfieldID);
jboolean    GetBooleanField(JNIEnv*, jobject, jfieldID);
jbyte       GetByteField(JNIEnv*, jobject, jfieldID);
jchar       GetCharField(JNIEnv*, jobject, jfieldID);
jshort      GetShortField(JNIEnv*, jobject, jfieldID);
jint        GetIntField(JNIEnv*, jobject, jfieldID);
jlong       GetLongField(JNIEnv*, jobject, jfieldID);
jfloat      GetFloatField(JNIEnv*, jobject, jfieldID);
jdouble     GetDoubleField(JNIEnv*, jobject, jfieldID);
// 獲取靜態(tài)字段值
jobject     GetStaticObjectField(JNIEnv*, jclass, jfieldID);
jboolean    GetStaticBooleanField(JNIEnv*, jclass, jfieldID);
jbyte       GetStaticByteField(JNIEnv*, jclass, jfieldID);
jchar       GetStaticCharField(JNIEnv*, jclass, jfieldID);
jshort      GetStaticShortField(JNIEnv*, jclass, jfieldID);
jint        GetStaticIntField(JNIEnv*, jclass, jfieldID);
jlong       GetStaticLongField(JNIEnv*, jclass, jfieldID);
jfloat      GetStaticFloatField(JNIEnv*, jclass, jfieldID);
jdouble     GetStaticDoubleField(JNIEnv*, jclass, jfieldID);


void SetObjectField(JNIEnv*, jobject, jfieldID, jobject);
void SetBooleanField(JNIEnv*, jobject, jfieldID, jboolean);
void SetByteField(JNIEnv*, jobject, jfieldID, jbyte);
void SetCharField(JNIEnv*, jobject, jfieldID, jchar);
void SetShortField(JNIEnv*, jobject, jfieldID, jshort);
void SetIntField(JNIEnv*, jobject, jfieldID, jint);
void SetLongField(JNIEnv*, jobject, jfieldID, jlong);
void SetFloatField(JNIEnv*, jobject, jfieldID, jfloat);
void SetDoubleField(JNIEnv*, jobject, jfieldID, jdouble);
// 設(shè)置靜態(tài)字段值
void SetStaticObjectField(JNIEnv*, jclass, jfieldID, jobject);
void SetStaticBooleanField(JNIEnv*, jclass, jfieldID, jboolean);
void SetStaticByteField(JNIEnv*, jclass, jfieldID, jbyte);
void SetStaticCharField(JNIEnv*, jclass, jfieldID, jchar);
void SetStaticShortField(JNIEnv*, jclass, jfieldID, jshort);
void SetStaticIntField(JNIEnv*, jclass, jfieldID, jint);
void SetStaticLongField(JNIEnv*, jclass, jfieldID, jlong);
void SetStaticFloatField(JNIEnv*, jclass, jfieldID, jfloat);
void SetStaticDoubleField(JNIEnv*, jclass, jfieldID, jdouble);

GetMethodID / CallXxxMethod
GetStaticMethodID / CallStaticXxxMethod

/*
 * @param env: JNI 接口指針。
 * @param clazz:一個 Java 類。
 * @param name:方法名稱,以 \0 結(jié)尾的 UTF-8 字符串。
 * @param sig:方法簽名,以 \0 結(jié)尾的 UTF-8 字符串。
 * @return 返回方法 ID,如果操作失敗返回 NULL。
 */
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
// 獲取靜態(tài)方法
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);


/*
 * 調(diào)用實例方法
 *
 * @param env: JNI 接口指針。
 * @param jobject: 一個 Java 對象。
 * @param methodID:java 函數(shù)的 methodID, 必須通過調(diào)用 GetMethodID() 來獲得。
 * @param ...:java 函數(shù)的參數(shù)。
 * @param args:java 函數(shù)的參數(shù)數(shù)組。
 * @param args:java 函數(shù)參數(shù)的 va_list。
 * @return 返回 Java 對象,無法構(gòu)造該對象則返回 NULL。
 */
jobject     CallObjectMethod(JNIEnv*, jobject, jmethodID, ...);
jobject     CallObjectMethodV(JNIEnv*, jobject, jmethodID, va_list);
jobject     CallObjectMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jboolean    CallBooleanMethod(JNIEnv*, jobject, jmethodID, ...);
jboolean    CallBooleanMethodV(JNIEnv*, jobject, jmethodID, va_list);
jboolean    CallBooleanMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jbyte       CallByteMethod(JNIEnv*, jobject, jmethodID, ...);
jbyte       CallByteMethodV(JNIEnv*, jobject, jmethodID, va_list);
jbyte       CallByteMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jchar      CallCharMethod(JNIEnv*, jobject, jmethodID, ...);
jchar      CallCharMethodV(JNIEnv*, jobject, jmethodID, va_list);
jchar      CallCharMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jshort     CallShortMethod(JNIEnv*, jobject, jmethodID, ...);
jshort     CallShortMethodV(JNIEnv*, jobject, jmethodID, va_list);
jshort     CallShortMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jint       CallIntMethod(JNIEnv*, jobject, jmethodID, ...);
jint       CallIntMethodV(JNIEnv*, jobject, jmethodID, va_list);
jint       CallIntMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jlong      CallLongMethod(JNIEnv*, jobject, jmethodID, ...);
jlong      CallLongMethodV(JNIEnv*, jobject, jmethodID, va_list);
jlong      CallLongMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jfloat     CallFloatMethod(JNIEnv*, jobject, jmethodID, ...);
jfloat     CallFloatMethodV(JNIEnv*, jobject, jmethodID, va_list);
jfloat     CallFloatMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
jdouble    CallDoubleMethod(JNIEnv*, jobject, jmethodID, ...);
jdouble    CallDoubleMethodV(JNIEnv*, jobject, jmethodID, va_list);
jdouble    CallDoubleMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
void       CallVoidMethod(JNIEnv*, jobject, jmethodID, ...);
void       CallVoidMethodV(JNIEnv*, jobject, jmethodID, va_list);
void       CallVoidMethodA(JNIEnv*, jobject, jmethodID, const jvalue*);
// 調(diào)用靜態(tài)方法
jobject     CallStaticObjectMethod(JNIEnv*, jclass, jmethodID, ...);
jobject     CallStaticObjectMethodV(JNIEnv*, jclass, jmethodID, va_list);
jobject     CallStaticObjectMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jboolean    CallStaticBooleanMethod(JNIEnv*, jclass, jmethodID, ...);
jboolean    CallStaticBooleanMethodV(JNIEnv*, jclass, jmethodID, va_list);
jboolean    CallStaticBooleanMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jbyte       CallStaticByteMethod(JNIEnv*, jclass, jmethodID, ...);
jbyte       CallStaticByteMethodV(JNIEnv*, jclass, jmethodID, va_list);
jbyte       CallStaticByteMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jchar      CallStaticCharMethod(JNIEnv*, jclass, jmethodID, ...);
jchar      CallStaticCharMethodV(JNIEnv*, jclass, jmethodID, va_list);
jchar      CallStaticCharMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jshort     CallStaticShortMethod(JNIEnv*, jclass, jmethodID, ...);
jshort     CallStaticShortMethodV(JNIEnv*, jclass, jmethodID, va_list);
jshort     CallStaticShortMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jint       CallStaticIntMethod(JNIEnv*, jclass, jmethodID, ...);
jint       CallStaticIntMethodV(JNIEnv*, jclass, jmethodID, va_list);
jint       CallStaticIntMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jlong      CallStaticLongMethod(JNIEnv*, jclass, jmethodID, ...);
jlong      CallStaticLongMethodV(JNIEnv*, jclass, jmethodID, va_list);
jlong      CallStaticLongMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jfloat     CallStaticFloatMethod(JNIEnv*, jclass, jmethodID, ...);
jfloat     CallStaticFloatMethodV(JNIEnv*, jclass, jmethodID, va_list);
jfloat     CallStaticFloatMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
jdouble    CallStaticDoubleMethod(JNIEnv*, jclass, jmethodID, ...);
jdouble    CallStaticDoubleMethodV(JNIEnv*, jclass, jmethodID, va_list);
jdouble    CallStaticDoubleMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);
void       CallStaticVoidMethod(JNIEnv*, jclass, jmethodID, ...);
void       CallStaticVoidMethodV(JNIEnv*, jclass, jmethodID, va_list);
void       CallStaticVoidMethodA(JNIEnv*, jclass, jmethodID, const jvalue*);

在 JNI 中調(diào)用 java 對象的變量或者方法時常常會用到 jfieldID 和 jmethodID。
可以看下面的例子:

JNIEXPORT void JNICALL Java_com_sample_MainActivity_stringFromJNI(
JNIEnv* env, jobject this_obj)
{  
   /* get the class */
   jclass class_obj = (*env)->GetObjectClass(env, this_obj);

   /* get the field ID */
   jfieldID id_age = (*env)->GetFieldID(env, class_obj, "age", "I");
   jfieldID id_name = (*env)->GetFieldID(env, class_obj, "name", "Ljava/lang/String;");

   /* get the field value */
   jint age = (*env)->GetIntField(env, this_obj, id_age);
   jstring age = (*env)->GetIntField(env, this_obj, id_age);

   age += 1;

   /* set the field value */
   (*env)->SetIntField(env, this_obj, id_age, age);

   jmethodID methodInActivity =
            env->GetMethodID(env->GetObjectClass(this_obj), "methodInActivity", "()V");
   env->CallVoidMethod(this_obj, methodInActivity);
}

JNI 類和對象

JNI 類

/*
 * @brief 定義新的類或接口
 *
 * @param env: JNI 接口指針.
 * @param name: 要定義的類或接口的名稱。
 * @param loader: 分配給已定義類的類加載器。
 * @param buf: 包含 .class 文件數(shù)據(jù)的緩沖區(qū)。
 * @param bufLen: 緩沖區(qū)長度。
 * @return 返回 Java 類。如果發(fā)生錯誤,則返回 NULL。
 */
jclass DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize bufLen);
/*
 * @brief 加載一個已經(jīng)定義過的類
 *
 * @param env: JNI 接口指針。
 * @param name: 完全限定的類名。例如 java.lang.String:java/lang/String。
 *              如果名稱以"["(數(shù)組簽名字符)開頭,則返回數(shù)組類。
 * @return 返回 Java 類。如果發(fā)生錯誤,則返回 NULL。
 */
jclass FindClass(JNIEnv *env, const char *name);
/*
 * @brief: 加載一個已經(jīng)定義過的類的父類
 *
 * @param env: JNI 接口指針。
 * @param clazz: 一個 Java 類。
 * @return 返回父類。如果發(fā)生錯誤,則返回 NULL。
 */
jclass GetSuperclass(JNIEnv *env, jclass clazz);
/*
 * @brief 判斷 clazz1 是否可以安全得轉(zhuǎn)換為 clazz2
 *
 * @param env: JNI 接口指針。
 * @param clazz1: 第一個類參數(shù)。
 * @param clazz2: 第二個類參數(shù)。
 * @return 如果可以轉(zhuǎn)換,則返回 JNI_TRUE
 */
jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);

JNI 對象

/*
 * @brief 創(chuàng)建新的 Java 對象,而無需調(diào)用該對象的任何構(gòu)造函數(shù)。返回該對象的引用。clazz 參數(shù)不能為任何數(shù)組類。
 *
 * @param env: JNI接口指針。
 * @param clazz: 一個 Java 類。
 * @return 返回 Java 對象,無法構(gòu)造該對象則返回 NULL。
 */
jobject AllocObject(JNIEnv *env, jclass clazz);
/*
 * @brief 創(chuàng)建新的 Java 對象,指定夠照方法
 *
 * @param env: JNI 接口指針。
 * @param clazz: 一個 Java 類。
 * @param methodID:構(gòu)造函數(shù)的 methodID。必須通過 GetMethodID() 獲取構(gòu)造方法的 methodID。
                    構(gòu)造方法名為 <init>,方法簽名為 (I)V、()V 等
 * @param ...:構(gòu)造函數(shù)的參數(shù)。
 * @param args:構(gòu)造函數(shù)的參數(shù)數(shù)組。
 * @param args:構(gòu)造函數(shù)參數(shù)的 va_list。
 * @return 返回 Java 對象,無法構(gòu)造該對象則返回 NULL。
 */
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
/*
 * @brief 獲取對象所屬的類
 *
 * @param env: JNI 接口指針。
 * @param obj: 一個 Java 對象(必須不是 NULL)。
 * @return Java 類。
 */
jclass GetObjectClass(JNIEnv *env, jobject obj);
/*
 * @brief 獲取 obj 的引用類型。
 *
 * @param env: JNI 接口指針。
 * @param obj: 局部引用、全局引用或者弱全局引用。
 * @return 返回以下枚舉值之一:
 *        - 如果 obj 不是有效的引用,則返回 JNIInvalidRefType = 0。
 *        - 如果 obj 是局部引用類型,則返回 JNILocalRefType = 1。
 *        - 如果 obj 是全局引用類型,則返回 JNIGlobalRefType = 2。
 *        - 如果 obj 是弱全局引用類型,則返回 JNIWeakGlobalRefType = 3。
 */
jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj);
/*
 * @brief 判斷對象是否是某個類的實例。
 *
 * @param env:JNI 接口指針。
 * @param obj:一個 Java 對象
 * @return JNI_TRUE 或者 JNI_FALSE。
 */
jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
/*
 * @brief 判斷兩個引用是否引用相同的 Java 對象。
 *
 * @param env: JNI 接口指針。
 * @param ref1:一個Java對象。
 * @param ref2:一個Java對象。
 * @return 如果 ref1 和 ref2 引用相同的 Java 對象,或者兩者均為 NULL,返回 JNI_TRUE; 否則返回 JNI_FALSE。
 */
jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);

JNI 類和對象例子

創(chuàng)建 Java 對象

jclass clazz = env->FindClass("java/lang/Integer");
if (clazz != nullptr) {
    jmethodID constructMethodId = env->GetMethodID(clazz, "<init>", "(I)V");
    jobject integerObject = env->NewObject(clazz, constructMethodId, jvalue);
}

獲取成員變量

extern "C" JNIEXPORT void JNICALL
Java_com_teletian_sample_myndk_MainActivity_testObject(JNIEnv *env, jobject this_obj) {
    jclass clazz = env->FindClass("com/teletian/sample/myndk/MainActivity");
    if (clazz != nullptr) {

        // 對應(yīng) MainActivity 中的 public int age = 1;
        jfieldID ageFieldId = env->GetFieldID(clazz, "age", "I");
        jint age_jint = env->GetIntField(this_obj, ageFieldId);

        // 對應(yīng) MainActivity 中的 public String name = "Jack";
        jfieldID nameFieldId = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
        jstring name_jstring = (jstring) env->GetObjectField(this_obj, nameFieldId);
        // 從 jsting 獲取 C 格式字符串
        // 關(guān)于 GetStringUTFChars 的詳細解釋,請參考下文
        char *name = (char *) env->GetStringUTFChars(name_jstring, nullptr);
        LOGI("name:%s; age:%d", name);
        env->ReleaseStringUTFChars(name_jstring, name);
    }
    // 雖然會自動釋放,但是手動釋放是個好習(xí)慣
    env->DeleteLocalRef(clazz);
}

調(diào)用對象方法

extern "C" JNIEXPORT void JNICALL
Java_com_teletian_sample_myndk_MainActivity_testObject(JNIEnv *env, jobject this_obj) {
    jclass clazz = env->FindClass("com/teletian/sample/myndk/MainActivity");
    if (clazz != nullptr) {
        // 對應(yīng) public int getAge() 方法
        jmethodID getAgeMethodId = env->GetMethodID(clazz, "getAge", "()I");
        env->CallIntMethod(this_obj, getAgeMethodId);

        // 對應(yīng) public void printMsg(String msg) 方法
        jmethodID printMsgMethodId = env->GetMethodID(clazz, "printMsg", "(Ljava/lang/String;)V");
        std::string msg = "age: " + std::to_string(age_jint);
        env->CallVoidMethod(this_obj, printMsgMethodId, env->NewStringUTF(msg.c_str()));
    }
    // 雖然會自動釋放,但是手動釋放是個好習(xí)慣
    env->DeleteLocalRef(clazz);
}

靜態(tài)字段和靜態(tài)方法

extern "C" JNIEXPORT void JNICALL
Java_com_teletian_sample_myndk_MainActivity_testObject(JNIEnv *env, jobject this_obj) {
    jclass clazz = env->FindClass("com/teletian/sample/myndk/MainActivity");
    if (clazz != nullptr) {
        // 對應(yīng) public static String KEY = "key";
        jfieldID keyFieldId = env->GetStaticFieldID(clazz, "KEY", "Ljava/lang/String;");
        jstring key_jsting = (jstring) env->GetStaticObjectField(clazz, keyFieldId);

        // 對應(yīng) public static String staticMethod(String name) 方法
        jmethodID staticMethodId = env->GetStaticMethodID(
                clazz, "staticMethod", "(Ljava/lang/String;)Ljava/lang/String;");
        jstring param_jsting = env->NewStringUTF("param");
        jstring return_jsting = (jstring) env->CallStaticObjectMethod(
                clazz, staticMethodId, param_jsting);

        // 從 jsting 獲取 C 格式字符串
        // 關(guān)于 GetStringUTFChars 的詳細解釋,請參考下文1
        const char *key = env->GetStringUTFChars(param_jsting, nullptr);
        const char *return_value = env->GetStringUTFChars(return_jsting, nullptr);
        LOGI("key:%s; return_value:%s", return_value, return_value);
        env->ReleaseStringUTFChars(key_jsting, key);
        env->ReleaseStringUTFChars(return_jsting, return_value);
    }
    // 雖然會自動釋放,但是手動釋放是個好習(xí)慣
    env->DeleteLocalRef(clazz);
}

JNI 字符串

NewString

創(chuàng)建 jsting 字符串

jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);

GetStringLength

獲取 jsting 長度

jsize GetStringLength(JNIEnv *env, jstring string);

GetStringChars

jsting轉(zhuǎn)換為 jchar 數(shù)組
isCopy 的解釋可以參考 GetStringUTFChars 的介紹

const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);

GetStringChars 獲取到的 jchar 數(shù)組使用完了要釋放

void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);

GetStringRegion

從字符串中的指定位置復(fù)制指定長度的字符到字符數(shù)組中

void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);

NewStringUTF

創(chuàng)建 UTF-8 jsting 字符串

jstring NewStringUTF(JNIEnv *env, const char *bytes);

GetStringUTFLength

獲取 UTF-8 jsting 長度

jsize GetStringUTFLength(JNIEnv *env, jstring string);

GetStringUTFChars

從 jstring 中獲取 char *

const char* GetStringUTFChars(jstring string, jboolean* isCopy)

isCopy 是一個 jboolean 引用,是作為返回值的,當它返回的值是非 nullptr 時,JNI_TRUE 代表復(fù)制了一份,JNI_FALSE 代表沒有復(fù)制。
這邊千萬別弄錯了,isCopy 不是讓你告訴系統(tǒng)需不需要復(fù)制的,而是作為返回值讓系統(tǒng)告訴你它有沒有復(fù)制一份。
這個有什么用呢?如果返回 JNI_TRUE 的話,就可以當成一個臨時的存儲,安全的使用了。

jboolean isCopy;
const char* something=env->GetStringUTFChars(somethingFromJava, &isCopy);

當然,如果對有沒有復(fù)制不關(guān)心,直接傳 nullptr 即可

const char* something=env->GetStringUTFChars(somethingFromJava, nullptr);

不管有沒有復(fù)制一份,使用完了都需要 release。(從 java 中 get 到的類和對象都需要 release)

env->ReleaseStringUTFChars(somethingFromJava, something);

GetStringUTFRegion

從 UTF-8 字符串中的指定位置復(fù)制指定長度的字符到字符數(shù)組中

void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);

字符串使用例

extern "C" JNIEXPORT jstring JNICALL Java_com_teletian_sample_myndk_MainActivity_testString(JNIEnv *env, 
                                                                                            jobject thiz, 
                                                                                            jstring s_jstring) {
    char *s = (char *) env->GetStringUTFChars(s_jstring, nullptr);
    // string& operator= (const char* s); 復(fù)制 s 到 ss
    std::string ss = s;
    ss.append("\n");
    ss.append("append");
    env->ReleaseStringUTFChars(s_jstring, s);
    return env->NewStringUTF(ss.c_str());
}

JNI 數(shù)組

創(chuàng)建 java 側(cè)的數(shù)組

jbooleanArray NewBooleanArray(JNIEnv*, jsize);
jbyteArray    NewByteArray(JNIEnv*, jsize);
jcharArray    NewCharArray(JNIEnv*, jsize);
jshortArray   NewShortArray(JNIEnv*, jsize);
jintArray     NewIntArray(JNIEnv*, jsize);
jlongArray    NewLongArray(JNIEnv*, jsize);
jfloatArray   NewFloatArray(JNIEnv*, jsize);
jdoubleArray  NewDoubleArray(JNIEnv*, jsize);
/*
 * @param elementClass:數(shù)組元素類。
 * @param initialElement:數(shù)組元素初值。
 */
jobjectArray  NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);

從 java 側(cè)數(shù)組獲取 C 側(cè)數(shù)組

// 基本類型數(shù)組獲取
// jboolean* isCopy 和上文 GetStringUTFChars 一樣
jboolean* GetBooleanArrayElements(JNIEnv*, jbooleanArray, jboolean*);
jbyte*    GetByteArrayElements(JNIEnv*, jbyteArray, jboolean*);
jchar*    GetCharArrayElements(JNIEnv*, jcharArray, jboolean*);
jshort*   GetShortArrayElements(JNIEnv*, jshortArray, jboolean*);
jint*     GetIntArrayElements(JNIEnv*, jintArray, jboolean*);
jlong*    GetLongArrayElements(JNIEnv*, jlongArray, jboolean*);
jfloat*   GetFloatArrayElements(JNIEnv*, jfloatArray, jboolean*);
jdouble*  GetDoubleArrayElements(JNIEnv*, jdoubleArray, jboolean*);

// 獲取指定范圍
void GetBooleanArrayRegion(JNIEnv*, jbooleanArray, jsize, jsize, jboolean*);
void GetByteArrayRegion(JNIEnv*, jbyteArray, jsize, jsize, jbyte*);
void GetCharArrayRegion(JNIEnv*, jcharArray, jsize, jsize, jchar*);
void GetShortArrayRegion(JNIEnv*, jshortArray, jsize, jsize, jshort*);
void GetIntArrayRegion(JNIEnv*, jintArray, jsize, jsize, jint*);
void GetLongArrayRegion(JNIEnv*, jlongArray, jsize, jsize, jlong*);
void GetFloatArrayRegion(JNIEnv*, jfloatArray, jsize, jsize, jfloat*);
void GetDoubleArrayRegion(JNIEnv*, jdoubleArray, jsize, jsize, jdouble*);

// 引用類型數(shù)組獲取
// 因為基本類型 C/C++ 里面也有,所以可以直接用數(shù)組去接,而引用類型 C/C++ 里面沒有,所以只能傳入索引一個一個獲取
// 獲取的還是 java 對象 jobject,所以不需要釋放
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);

設(shè)置數(shù)組值

// 基本類型(只有指定范圍的版本)
void SetBooleanArrayRegion(JNIEnv*, jbooleanArray, jsize, jsize, const jboolean*);
void SetByteArrayRegion(JNIEnv*, jbyteArray, jsize, jsize, const jbyte*);
void SetCharArrayRegion(JNIEnv*, jcharArray, jsize, jsize, const jchar*);
void SetShortArrayRegion(JNIEnv*, jshortArray, jsize, jsize, const jshort*);
void SetIntArrayRegion(JNIEnv*, jintArray, jsize, jsize, const jint*);
void SetLongArrayRegion(JNIEnv*, jlongArray, jsize, jsize, const jlong*);
void SetFloatArrayRegion(JNIEnv*, jfloatArray, jsize, jsize, const jfloat*);
void SetDoubleArrayRegion(JNIEnv*, jdoubleArray, jsize, jsize, const jdouble*);

// 引用類型
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);

使用完了要釋放(在釋放之前,C 中的操作不會影響 Java 的原數(shù)組,但是一旦釋放了,C 中所做的修改會反應(yīng)到 Java 原數(shù)組)

void ReleaseBooleanArrayElements(JNIEnv*, jbooleanArray, jboolean*, jint);
void ReleaseByteArrayElements(JNIEnv*, jbyteArray, jbyte*, jint);
void ReleaseCharArrayElements(JNIEnv*, jcharArray, jchar*, jint);
void ReleaseShortArrayElements(JNIEnv*, jshortArray, jshort*, jint);
void ReleaseIntArrayElements(JNIEnv*, jintArray, jint*, jint);
void ReleaseLongArrayElements(JNIEnv*, jlongArray, jlong*, jint);
void ReleaseFloatArrayElements(JNIEnv*, jfloatArray, jfloat*, jint);
void ReleaseDoubleArrayElements(JNIEnv*, jdoubleArray, jdouble*, jint);

// 引用類型數(shù)組不需要 release,原因上文的創(chuàng)建數(shù)組部分已經(jīng)講過。但是需要 deleteLocalRef

獲取數(shù)組長度

jsize GetArrayLength(JNIEnv *env, jarray array);

數(shù)組使用例

MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    TextView tv = findViewById(R.id.sample_text);
    int[] arr1 = {1, 1};
    String[] arr2 = {"java value"};
    String s = "result:" + Arrays.toString(testArray(arr1, arr2))
            + "\n" + "arr1:" + Arrays.toString(arr1)
            + "\n" + "arr2:" + Arrays.toString(arr2);
    tv.setText(s);
}

public native int[] testArray(int[] arr1, String[] arr2);

native-lib.cpp

extern "C" JNIEXPORT jintArray JNICALL 
Java_com_teletian_sample_myndk_MainActivity_testArray(JNIEnv *env,
                                                    jobject thiz, 
                                                    jintArray arr1, 
                                                    jobjectArray arr2) {
    // 基本類型數(shù)組
    jint* _arr1 = env->GetIntArrayElements(arr1, nullptr);
    int length1 = env->GetArrayLength(arr1);
    for (int i = 0; i < length1; i++) {
        _arr1[i] = 2; // 修改數(shù)組值。在這里,只修改了 _aar1 的值,aar1 的值不變
    }
    // 一旦 ReleaseIntArrayElements 調(diào)用了,對 _aar1 的修改會反應(yīng)到原數(shù)組 aar1 中去
    env->ReleaseIntArrayElements(arr1, _arr1, 0);

    // 引用數(shù)組,只能根據(jù) index 取單個值
    jstring _arr2 = (jstring) env->GetObjectArrayElement(arr2, 0);
    const char* s = env->GetStringUTFChars(_arr2, nullptr);
    LOGD("[testArray] old arr2[0]:%s", s);
    jstring newArr2 = env->NewStringUTF("JNI value");
    env->SetObjectArrayElement(arr2, 0, newArr2);

    // create new array
    int array[2] = {3, 3};
    jintArray dst = env->NewIntArray(2);
    env->SetIntArrayRegion(dst, 0, 2, array);
    return dst;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容