美文网首页Android
Android NDK(二)- JNI 基础

Android NDK(二)- JNI 基础

作者: teletian | 来源:发表于2022-06-18 13:43 被阅读0次

    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

    类型签名

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

    相关文章

      网友评论

        本文标题:Android NDK(二)- JNI 基础

        本文链接:https://www.haomeiwen.com/subject/dbllhrtx.html