Android NDK(二)- JNI 基础

JNI 类型

JNI 中有许多和 Java 相对应的类型

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

关于 JNI 中基本数据类型和 C++ 中的基本数据类型,它们是可以直接互相转化的,不需要特别的操作。参照 jni.h 文件可以发现,其实就是换了个名字而已,其实本质是一个东西。

/* 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 释放了,指针仍然指向一个地址,只是对应的地址中数据已经被释放了

JNI 的引用分为四种:

  1. 全局引用(GlobalReferences):全局有效。JVM 无法释放回收,必须通过调用 DeleteGlobalRef() 显式释放。
    创建全局引用:jobject NewGlobalRef(JNIEnv *env, jobject obj);
    释放全局引用:void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

  2. 弱全局引用(WeakGlobalReferences):一种特殊的全局引用,可以被 JVM 回收。
    创建弱全局引用:jobject NewWeakGlobalRef(JNIEnv *env, jobject obj);
    释放弱全局引用:void DeleteWeakGlobalRef(JNIEnv *env, jobject globalRef);

  3. 局部引用(LocalReferences):在方法内创建,方法结束后自动释放。虽然会在方法结束后自动释放,但是如果消耗过多 JVM 资源,也可以手动释放。
    创建局部引用:jobject NewLocalRef(JNIEnv *env, jobject obj);
    释放局部引用:void DeleteLocalRef(JNIEnv *env, jobject globalRef);

    虽然方法结束会自动释放,但是建议使用完了就手动释放。尤其以下两种情况必须手动释放:

    1. 引用一个很大的 Java 对象
    2. 在 for 循环中创建了大量的引用。引用多了之后会报 ReferenceTable overflow 异常。

    哪些场景需要释放?JNI 函数内部创建的 jobject、jclass、jstring、jarray 等引用都需要释放。

    • FindClass / DeleteLocalRef
    • NewString / DeleteLocalRef
    • NewStringUTF / DeleteLocalRef
    • NewObject / DeleteLocalRef
    • NewXxxArray / DeleteLocalRef
    • GetObjectField / DeleteLocalRef
    • GetObjectClass / DeleteLocalRef
    • GetObjectArrayElement / DeleteLocalRef
    • 注意:对于 GetStringChars、GetStringUTFChars、GetXxxArrayElements 基本类型数组,需要调用对应的 Release 方法去释放本地内存

  4. 无效引用(InvalidReferences):无效引用一般情况下没有什么用,不展开介绍。

字段和方法 ID

jfieldID 和 jmethodID 是常规的 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 结尾的 UTF-8 字符串。
 * @param sig:字段签名,以 \0 结尾的 UTF-8 字符串。
 * @return 返回字段 ID,如果操作失败返回 NULL。
 */
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
// 获取静态字段
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);
// 获取静态字段值
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);
// 设置静态字段值
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 结尾的 UTF-8 字符串。
 * @param sig:方法签名,以 \0 结尾的 UTF-8 字符串。
 * @return 返回方法 ID,如果操作失败返回 NULL。
 */
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
// 获取静态方法
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);


/*
 * 调用实例方法
 *
 * @param env: JNI 接口指针。
 * @param jobject: 一个 Java 对象。
 * @param methodID:java 函数的 methodID, 必须通过调用 GetMethodID() 来获得。
 * @param ...:java 函数的参数。
 * @param args:java 函数的参数数组。
 * @param args:java 函数参数的 va_list。
 * @return 返回 Java 对象,无法构造该对象则返回 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*);
// 调用静态方法
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 中调用 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 文件数据的缓冲区。
 * @param bufLen: 缓冲区长度。
 * @return 返回 Java 类。如果发生错误,则返回 NULL。
 */
jclass DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize bufLen);
/*
 * @brief 加载一个已经定义过的类
 *
 * @param env: JNI 接口指针。
 * @param name: 完全限定的类名。例如 java.lang.String:java/lang/String。
 *              如果名称以"["(数组签名字符)开头,则返回数组类。
 * @return 返回 Java 类。如果发生错误,则返回 NULL。
 */
jclass FindClass(JNIEnv *env, const char *name);
/*
 * @brief: 加载一个已经定义过的类的父类
 *
 * @param env: JNI 接口指针。
 * @param clazz: 一个 Java 类。
 * @return 返回父类。如果发生错误,则返回 NULL。
 */
jclass GetSuperclass(JNIEnv *env, jclass clazz);
/*
 * @brief 判断 clazz1 是否可以安全得转换为 clazz2
 *
 * @param env: JNI 接口指针。
 * @param clazz1: 第一个类参数。
 * @param clazz2: 第二个类参数。
 * @return 如果可以转换,则返回 JNI_TRUE
 */
jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);

JNI 对象

/*
 * @brief 创建新的 Java 对象,而无需调用该对象的任何构造函数。返回该对象的引用。clazz 参数不能为任何数组类。
 *
 * @param env: JNI接口指针。
 * @param clazz: 一个 Java 类。
 * @return 返回 Java 对象,无法构造该对象则返回 NULL。
 */
jobject AllocObject(JNIEnv *env, jclass clazz);
/*
 * @brief 创建新的 Java 对象,指定够照方法
 *
 * @param env: JNI 接口指针。
 * @param clazz: 一个 Java 类。
 * @param methodID:构造函数的 methodID。必须通过 GetMethodID() 获取构造方法的 methodID。
                    构造方法名为 <init>,方法签名为 (I)V、()V 等
 * @param ...:构造函数的参数。
 * @param args:构造函数的参数数组。
 * @param args:构造函数参数的 va_list。
 * @return 返回 Java 对象,无法构造该对象则返回 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 类和对象例子

创建 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) {

        // 对应 MainActivity 中的 public int age = 1;
        jfieldID ageFieldId = env->GetFieldID(clazz, "age", "I");
        jint age_jint = env->GetIntField(this_obj, ageFieldId);

        // 对应 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 格式字符串
        // 关于 GetStringUTFChars 的详细解释,请参考下文
        char *name = (char *) env->GetStringUTFChars(name_jstring, nullptr);
        LOGI("name:%s; age:%d", name);
        env->ReleaseStringUTFChars(name_jstring, name);
    }
    // 虽然会自动释放,但是手动释放是个好习惯
    env->DeleteLocalRef(clazz);
}

调用对象方法

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) {
        // 对应 public int getAge() 方法
        jmethodID getAgeMethodId = env->GetMethodID(clazz, "getAge", "()I");
        env->CallIntMethod(this_obj, getAgeMethodId);

        // 对应 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()));
    }
    // 虽然会自动释放,但是手动释放是个好习惯
    env->DeleteLocalRef(clazz);
}

静态字段和静态方法

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) {
        // 对应 public static String KEY = "key";
        jfieldID keyFieldId = env->GetStaticFieldID(clazz, "KEY", "Ljava/lang/String;");
        jstring key_jsting = (jstring) env->GetStaticObjectField(clazz, keyFieldId);

        // 对应 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 格式字符串
        // 关于 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);
    }
    // 虽然会自动释放,但是手动释放是个好习惯
    env->DeleteLocalRef(clazz);
}

JNI 字符串

NewString

创建 jsting 字符串

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

GetStringLength

获取 jsting 长度

jsize GetStringLength(JNIEnv *env, jstring string);

GetStringChars

jsting转换为 jchar 数组
isCopy 的解释可以参考 GetStringUTFChars 的介绍

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

GetStringChars 获取到的 jchar 数组使用完了要释放

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

GetStringRegion

从字符串中的指定位置复制指定长度的字符到字符数组中

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

NewStringUTF

创建 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 代表复制了一份,JNI_FALSE 代表没有复制。
这边千万别弄错了,isCopy 不是让你告诉系统需不需要复制的,而是作为返回值让系统告诉你它有没有复制一份。
这个有什么用呢?如果返回 JNI_TRUE 的话,就可以当成一个临时的存储,安全的使用了。

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

当然,如果对有没有复制不关心,直接传 nullptr 即可

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

不管有没有复制一份,使用完了都需要 release。(从 java 中 get 到的类和对象都需要 release)

env->ReleaseStringUTFChars(somethingFromJava, something);

GetStringUTFRegion

从 UTF-8 字符串中的指定位置复制指定长度的字符到字符数组中

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); 复制 s 到 ss
    std::string ss = s;
    ss.append("\n");
    ss.append("append");
    env->ReleaseStringUTFChars(s_jstring, s);
    return env->NewStringUTF(ss.c_str());
}

JNI 数组

创建 java 侧的数组

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:数组元素类。
 * @param initialElement:数组元素初值。
 */
jobjectArray  NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);

从 java 侧数组获取 C 侧数组

// 基本类型数组获取
// 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*);

// 引用类型数组获取
// 因为基本类型 C/C++ 里面也有,所以可以直接用数组去接,而引用类型 C/C++ 里面没有,所以只能传入索引一个一个获取
// 获取的还是 java 对象 jobject,所以不需要释放
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);

设置数组值

// 基本类型(只有指定范围的版本)
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 的原数组,但是一旦释放了,C 中所做的修改会反应到 Java 原数组)

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);

// 引用类型数组不需要 release,原因上文的创建数组部分已经讲过。但是需要 deleteLocalRef

获取数组长度

jsize GetArrayLength(JNIEnv *env, jarray array);

数组使用例

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) {
    // 基本类型数组
    jint* _arr1 = env->GetIntArrayElements(arr1, nullptr);
    int length1 = env->GetArrayLength(arr1);
    for (int i = 0; i < length1; i++) {
        _arr1[i] = 2; // 修改数组值。在这里,只修改了 _aar1 的值,aar1 的值不变
    }
    // 一旦 ReleaseIntArrayElements 调用了,对 _aar1 的修改会反应到原数组 aar1 中去
    env->ReleaseIntArrayElements(arr1, _arr1, 0);

    // 引用数组,只能根据 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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容