zoukankan      html  css  js  c++  java
  • Android JNI访问Java成员

    在 JNI 调用中,不仅仅 Java 可以调用本地方法,本地方法也可以调用 Java 中的方法和成员变量。

    Java 中的类封装了属性和方法,想要访问 Java 中的属性和方法,首先要获得 Java 类或 Java 对象,然后再访问属性、调用方法。

    在 Java 中类成员指静态属性和静态方法,它们属于类而不属于对象。而对象成员是指非静态属性和非静态方法,他们属于具体一个对象,不同的对象其成员是不同的,所以在本地代码中,对类成员的访问和对对象成员的访问是不同的。

    1、获取 Java 类的两种方式

    (1)通过传入JNI中的完整类名来获取类

    // name:类全名,包含包名,包名间隔符用 “/”
    jclass FindClass(const char *name);
    
    // JNI获得Android中的类并保存在jActivity中
    jclass jcls = env->FindClass("com/aaron/link/LedNative");

    (2)通过传入JNI中的一个java的对象来获取该对象的类

    // obj: 引用类型
    jclass GetObjectClass(jobject obj);
    
    // JNI获得引用obj所对应的类
    jclass myCls = env->GetObjectClass(obj);

    2、获取 Java 属性 ID 和方法 ID

     在本地代码中要访问设置 Java 属性和方法,首先要在本地代码中取得代表该 Java 属性的 jfieldID 和代表该 Java 方法的 jmethodID,然后才能进行属性操作和方法调用。

    // clazz:要取的成员对应的类
    // name:要取的方法或者属性
    // sig:要取的方法或属性的签名
    
    // 根据属性签名返回 clazz 类中的该属性 ID
    jfieldID GetFieldID(jclass clazz, const char *name, const char *sig);
    // 根据属性签名返回 clazz 类中的静态属性 ID
    jfieldID GetStaticFieldID(jclass clazz, const char *name, const char *sig);
    // 根据方法签名返回 clazz 类中的该方法 ID
    jmethodID GetMethodID(jclass clazz, const char *name, const char *sig);
    // 根据方法签名返回 clazz 类中的静态方法 ID
    jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig);

     举例:

    Java代码

    class MyClass {
        private int mNumber;
        private static String mName = "Aaron";
        public MyClass() {
            mNumber = 100;
        }
        
        public void printNum() {
            System.out.println("Number:" + mNumber);
        }
        
        public static void printName() {
            System.out.println("Name:" + mName);
        }
    }
    
    class NativeCallJava {
        static {
            System.loadLibrary("native_callback");
        }
        
        private static native void callNative(MyClass cls);
        
        public static void main(String arg[]) {
            callNative(new MyClass());
        }
    }

     本地代码

    void JNI_callNative(JNIEnv *env, jclass thiz, jobject obj)
    {
        // 获取对象对应的类
        jclass myCls = env->GetObjectClass(obj);
        // 获取属性
        jfieldID mNumFieldID = env->GetFieldID(myCls, "mNumber", "I");
        // 获取静态属性
        jfieldID mNameFieldID = env->GetStaticFieldID(myCls, "mName", "java/lang/String");
        // 获取方法
        jmethodID printNumMethodID = env-GetMethodID(myCls, "printNum", "(V)V");
        // 获取静态方法
        jmethodID printNameMethodID = env-GetStaticMethodID(myCls, "printName", "(V)V");
    }

     3、JNI 类型签名

    Java 语言是面向对象的语言,支持重载机制,即允许多个具有相同的方法名不同的方法签名的方法存在。

    不能只通过方法名明确的让 JNI 找到 Java 对应的方法,还要指定方法的签名,即参数列表和返回值类型。

    JNI 签名类型
    类型签名 Z B C S I J F D L V [ [I [F [B [C [S [D [J [Z
    Java 类型 boolean byte char short int long float double void [] int[] float[] byte[] char[] short[] double[] long[] boolean[]

     基本类型

    以特定的单个大写字母表示

    Java类类型

    Java 类类型以 L 开头,以 “/” 分隔包名,在类名后加上 “;” 分割符,例如:String 的签名为:Ljava/lang/String

    在 Java 中数组是引用类型,数组以 “[” 开头,后面跟数组元素类型签名,例如:int[] 的签名是 [I,对于二维数组,int[][] 签名是 [[I,object 数组签名就是 [Ljava/lang/Object

    对于方法签名,在 JNI 中有特定的表示方式:(参数1类型签名参数2类型签名参数3类型签名... ...)返回值类型签名

    注意:

    (1)方法名在方法签名中没有体现出来。

    (2)括号内表示参数列表,参数列表紧密相连,中间没有逗号,没有空格。

    (3)返回值出现在括号后面。

    (4)没有返回值也要加上 V 类型。

    JNI 方法签名举例
    Java 方法 JNI 方法签名
    boolean isLedOn(void); (V)Z
    void setLedOn(int ledNo); (I)V
    String substr(String str, int idx, int count); (Ljava/lang/String;II)Ljava/lang/String
    char fun(int n, String s, int[] value); (ILjava/lang/String;[I)C
    boolean showMsg(android.View v, String msg); (Lanfroid/View;Ljava/lang/String;)Z

    4、JNI 操作 Java 属性和方法

    (1)获取、设置属性值和静态属性值

    取得了代表属性和静态属性的 jfieldID,就可以使用 JNIEnv 中提供的方法来获取、设置属性值和静态属性值。

    // <type>表示 Java 中的基本类型
    // 获取属性值的 JNI 方法
    j<type> Get<type>Field(jobject obj, jfieldID fieldID);
    j<type> GetStatic<type>Field(jobject obj, jfieldID fieldID);
    // 设置属性值的 JNI 方法
    void Set<type>Field(jobject obj, jfieldID fieldID, j<type> val);
    void SetStatic<type>Field(jobject obj, jfieldID fieldID, j<type> val);

    (2)通过 JNI 调用 Java 中的方法

    取得了代表方法的 jmethodID,就可以使用 JNIEnv 中提供的方法来调用 Java 中的方法。

    // type 是这个方法的返回值类型,首字母大写
    // 第一个参数代表调用的这个方法所属于的对象,或者这个静态方法所属的类。
    // 第二个参数代表 jmethodID,后面的表示调用方法的参数列表,...表示变长参数。
    // 调用 Java 成员方法
    Call<type>Method(jobject obj, jmethodID method, ...);
    // 调用 Java 静态成员方法
    CallStatic<type>Method(jobject obj, jmethodID method, ...);

    代码举例

    // 静态方法不依赖于任何对象就可以进行访问
    // 静态的直接通过类 myCls 来调用,非静态需要通过对象 obj 来调用
    void JNI_callNative(JNIEnv *env, jclass thiz, jobject obj)
    {
        // 获取对象对应的类
        jclass myCls = env->GetObjectClass(obj);
        // 获取属性
        jfieldID mNumFieldID = env->GetFieldID(myCls, "mNumber", "I");
        // 获取静态属性
        jfieldID mNameFieldID = env->GetStaticFieldID(myCls, "mName", "java/lang/String");
        
        // 获取.设置 Java 成员的属性值
        jint mNum = env->GetIntField(obj, mNumFieldID);
        env->SetIntField(obj, mNumFieldID, mNum*2);
        
        // 获取.设置 Java 静态属性值
        jstring mName = (jstring)(env->GetStaticObjectField(myCls, mNameFieldID));
        jstring newStr = env->NewStringUTF("Hello Native");
        env->SetStaticObjectField(myCls, mNameFieldID, newStr);
        
        // 获取方法
        jmethodID printNumMethodID = env-GetMethodID(myCls, "printNum", "(V)V");
        // 获取静态方法
        jmethodID printNameMethodID = env-GetStaticMethodID(myCls, "printName", "(V)V");
        
        // 调用 MyClass 对象中的 printNum 方法
        CallVoidMethod(obj, printNumMethodID);
        // 调用 Myclass 类的静态 printName 方法
        CallStaticVoidmethod(myCls, printNameMethodID);
    }

    5、在 JNI 中创建 Java 对象

    (1)在 JNI 中创建 Java 对象

    // JNIEnv 中创建 Java 对象的方法
    // clazz:要创建的对象的类
    // jmethodID:创建对象对应的构造方法ID
    // 参数列表:...表示是变长参数,以“V”结尾的方法名表示向量表表示参数列表,以“A”结尾的方法名表示以 jvalue 数组提供参数列表
    jobject NewObject(jclass clazz, jmethodID methodID, ...);
    jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args);
    jobject NewObjectA(jclass clazz, imethodID methodID, const jvalue *args);

    获得构造方法 ID 的方法 env->GetMethodID(clazz, method_name, sig) 中的第二个参数固定为类名(也可以用“<init>”代替类名),第三个参数和要调用的构造方法有关,默认的构造方法没有参数和返回值。

    void JNI_callNativa(JNIEnv *env, jclass thiz, jobject obj)
    {
        jclass myCls = env->GetObjectClass(obj);
        // 也可以通过完整类名获取
        //jclass myCls = env->FindClass("com/test/native/MyClass");
        // 获得 MyClass 的构造方法 ID
        jmethodID myClassMethodID = env->GetMethodID(myCls, "MyClass", "(V)V");
        // 创建 MyClass 对象
        jobject newObj = NewObject(myCls, myClassMethodID);
    }

    (2)在 JNI 中创建 Java String 对象

    在 Java 中,字符串 String 对象是 Unicode(UTF-16)编码,每个字符不论是中文还是英文还是符号,一个字符总是占用两个字节。在 C/C++ 中一个字符是一个字节,C/C++ 中的宽字符是两个字节的。

    在本地 C/C++ 代码中我们可以通过一个宽字符串,或是一个 UTF-8 编码的字符串创建一个 Java 端的 String 对象。这种情况通常用于返回 Java 环境一个 String 返回值等场合。

    // 根据传入的宽字符串创建一个 Java String 对象
    jstring NewString(const jchar *unicode, jsize len);
    // 根据传入的 UTF-8 字符串创建一个 Java String 对象
    jstring NewStringUTF(const char *utf);

     在 Java 中 String 类有很多对字符串进行操作的方法,在本地代码中通过 JNI 接口可以将 Java 的字符串转换到 C/C++ 的宽字符串(wchar_t*),或是传回一个 UTF-8 的字符串(char*)到 C/C++,在本地代码中操作。

    // 在 Java 端有一个字符串 String str = "abcd"; ,在本地代码中取得并输出
    void native_string_operation(JNIEnv *env, jobject obj)
    {
        // 取得该字符串的 jfieldID
        jfieldID id_string = env->GetFieldID(env->GetObjectClass(obj), "str", "Ljava/lang/String");
        // 取得该字符串,强制转换为 jstring 类型
        jstring string = (jstring)(env->GetObjectField(obj, id_string));
        printf("%s
    ", string);
    }

    JNIEnv 提供了一系列的方法来操作字符串:

    // str:传入一个指向 Java 中 String 对象的 jstring 引用
    // isCopy:传入一个 jboolean 的指针,其值可以为 NULL/JNI_TRUE/JNI_FALSE
    // JNI_TRUE:表示在本地开辟内存,然后把 Java 中的 String 复制到这个内存中,然后返回指向这个内存地址的指针
    // JNI_FALSE:表示直接返回指向 Java 中 String 的内存指针,这时不要改变这个内存的内容,这将破坏 String 在 Java 中始终是常量的规则
    // NULL:表示不关心是否复制字符串
    
    // 将一个 jstring 对象,转换为(UTF-16)编码的宽字符串(jchar*)
    const jchar *GetStringChars(jstring str, jboolean *isCopy);
    // 将一个 jstring 对象,转换为(UTF-8)编码的宽字符串(char*)
    const char *GetStringUTFChars(jstring str, jboolean *isCopy);

    使用这两个方法取得的字符串,在不用的时候都要释放,分别对应下面连个方法。

    // jstr:需要释放的本地字符串的资源
    // str:需要释放的本地字符串
    RealeaseStringChars(jstring jstr, const jchar *str);
    RealeaseStringUTFChars(jstring jstr, const char *str);

    6、在 JNI 中处理 Java 数组

    可以使用 GetFieldID 获取一个 Java 数组变量的 ID,然后用 GetObjectField 取得该数组到本地方法,返回值为 jobject,然后可以强制转换为 j<type>Array 类型。

    j<type>Array 类型是 JNI 定义的一个对象类型,它并不是 C/C++ 的数组,如 int[]等,所以要把 j<type>Array 转换为 C/C++ 中的数组来操作。

    JNIEnv 定义了一系列的方法来把一个 j<type>Array 类型转换为 C/C++ 数组或把 C/C++ 数组转换为 j<type>Array。

    (1)获取数组长度

    jsize GetArrayLength(jarray array);

    (2)对象类型数组操作

    // len:新创建对象数组长度
    // clazz:对象数组元素类型
    // init:对象数组元素的初始值
    // array:要操作的数组
    // index:要操作数组元素的下标
    // val:要设置的数组元素的值
    
    // 创建对象数组
    jobjectArray NewObjectArray(jsize len, jclass clazz, jobject init);
    // 获得元素
    jobject GetObjectArrayElement(jobjectArray array, jsize index);
    // 设置元素
    void SetObjectArrayElement(jobjectArray array, jsize index, jobject val);

    JNI 没有提供直接把 Java 的对象类型数组(Object[])直接转到 C++ 中的 jobject[] 数组的方法,而是直接通过 Get/SetObjectArrayElement 这样的方法来对 Java 的 Object[] 数组进行操作。

    (3)对基本数据类型数组的操作

    // 获得指定类型的数组
    j<type>* Get<type>ArrayElement(j<type>Array array, jboolean *isCopy);
    // 释放数组
    void Release<type>ArrayElements(j<type>Array array, j<type> *elems, jint mode);

    这类函数可以把 Java 基本类型的数组转换到 C/C++ 中的数组。有两种处理方式,一是复制一份传回本地代码,另一种是把指向 Java 数组的指针直接传回到本地代码,处理完本地化的数组后,通过 Realease<type>ArrayElements 来释放数组。处理方式有 Get 方法的第二个参数 isCopy 来决定(取值为 JNI_TRUE 或 JNI_FALSE)。

    第三个参数 mode 可以取下面的值:

    <1> 0:对 Java 的数组进行更新并释放 C/C++ 的数组

    <2> JNI_COMMIT:对 Java 的数组进行更新但是不释放 C/C++ 的数组

    <3> JNI_ABORT:对 Java 的数组不进行更新,释放 C/C++ 的数组

    Java:

    class ArrayTest {
        static {
            System.loadLibrary("native_array");
        }
        
        private int[] array = new int[]{1, 2, 3, 4, 5};
        
        public native void show();
        
        public static void main(String[] args) {
            new ArrayTest().show();
        }
    }

    JNI:

    void JNI_Array_show(JNIEnv *env, jobject obj)
    {
        jfieldID id_array = env->GetFieldID(env->GetObjectClass(obj), "array", "[I");
        jintArray arr = (jintArray)(env->GetObjectField(obj, id_array));
        jint *int_arr = env->GetIntArrayElements(arr, NULL);
        jsize len = env->GetArrayLength(arr);
        
        for(int i; i<len; i++)
            cout << int_arr[i] << endl;
            
        env->ReleaseIntArrayElements(arr, int_arr, JNI_ABORT);
    }
  • 相关阅读:
    kubernetes架构部署
    GitLab+Jenkins+Ansible
    python之字典方法
    Python之列表方法
    python之字符串方法
    Python编写从ZabbixAPI获取信息
    Django基础
    扫描某个包下所有的类,输出所有使用了特定注解的类的注解值
    日志切面和统一异常处理
    Mybatis动态排序问题
  • 原文地址:https://www.cnblogs.com/lialong1st/p/8991802.html
Copyright © 2011-2022 走看看