Android JNI开发系列:第三章 对引用数据类型的操作

    xiaoxiao2025-07-08  15

    项目代码:https://github.com/VincentWei95/ndk

    Android JNI开发系列:第一章 JNIEnv接口指针

    Android JNI开发系列:第二章 数据类型

    Android JNI开发系列:第三章 对引用数据类型的操作

    Android JNI开发系列:第四章 异常处理

    Android JNI开发系列:第五章 局部和全局引用

    Android JNI开发系列:第六章 线程

    Android JNI开发系列:第七章 POSIX线程

    Android JNI开发系列:第八章 POSIX Socket API 面向连接的通信

    Android JNI开发系列:第九章 POSIX Socket API 无连接的通信

    Android JNI开发系列:第十章 POSIX Socket API 本地通信

    文章目录

    1 字符串操作1.1 创建字符串1.2 java字符串转换成C字符串1.3 字符串操作demo 2 数组操作2.1 创建数组2.2 访问数组元素2.3 对副本操作2.3.1 将java数组复制到C数组中2.3.2 将C数组复制给java数组 2.4 对直接指针操作(操作数组元素)2.5 数组操作demo 3 NIO操作3.1 创建直接字节缓冲区3.2 获取直接字节缓冲区 4 JNI访问java的成员变量和方法总结5 JNI访问java对象的成员变量5.1 通过对象引用获取类5.2 获取java对象的成员变量id5.3 获取java对象的静态成员变量id5.4 获取java对象的成员变量5.5 获取成员变量demo 6 JNI调用java对象中的方法6.1 获取实例方法的id6.2 获取静态方法的id6.3 调用实例方法6.4 调用静态方法6.5 调用方法demo 7 域和方法描述符(JNI调用获取id时需要用到的类型签名声明)

    以下介绍的demo操作只列出相关头文件和源文件代码,具体AS配置JNI步骤操作请到该链接查看:

    https://blog.csdn.net/qq_31339141/article/details/90314170

    引用类型以不透明的引用方式传递给原生代码,而不是以原生数据类型的形式呈现(就是在java中的native方法传递对象实例参数与jni中的函数参数不一样,是经过转换的),因此引用类型不能直接使用和修改。

    JNI提供了一系列引用数据类型操作的API:

    字符串

    数组

    NIO缓冲区

    字段

    方法

    1 字符串操作

    JNI提供了java字符串与C字符串之间相互转换的必要函数。因为java字符串对象是不可变的,因此JNI不提供任何修改现有java字符串内容的函数。

    JNI支持Unicode编码格式和UTF-8编码格式的字符串。

    JNI函数的调用如果发生内存溢出的情况,将会返回NULL以通知原生代码虚拟机抛出异常,这样代码就会停止运行。

    1.1 创建字符串

    jstring javaString; // 创建Unicode编码格式的字符串 javaString = (*env)->NewString(env, "Hello world"); // 创建UTF-8编码格式的字符串 javaString = (*env)->NewStringUTF(env, "Hello world!");

    1.2 java字符串转换成C字符串

    const jbyte* str; jboolean isCopy; // 将Unicode编码格式的java字符串转换为C字符串 // 第三个参数可选,表示让调用者确定返回的C字符串地址指向副本还是指向堆中的固定对象(也就是将java字符串复制了一份,让C字符串地址指向复制的副本) str = (*env)->GetStringChars(env, javaString, &isCopy); // 将UTF-8编码格式的java字符串转换为C字符串 str = (*env)->GetStringUTFChars(env, javaString, &isCopy); if (str != 0) { printf("Java string:%s", str); if (isCopy == JNI_TRUE) { print("C string is a copy of the java string"); } else { printf("C string points to actual string"); } } // 释放字符串,防止内存泄漏 (*env)->ReleaseStringChars(env, javaString, str); (*env)->ReleaseStringUTFChars(env, javaString, str);

    1.3 字符串操作demo

    com_example_ndk_JNITest.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_ndk_JNITest */ #ifndef _Included_com_example_ndk_JNITest #define _Included_com_example_ndk_JNITest #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_ndk_JNITest * Method: getStringFromJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_ndk_JNITest_getStringFromJNI (JNIEnv *, jobject); /* * Class: com_example_ndk_JNITest * Method: javaStringToCString * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaStringToCString (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif com_example_ndk_JNITest.c #include "/include/com_example_ndk_JNITest.h" #include <jni.h> #include <stdio.h> #include <syslog.h> /* * Class: com_example_ndk_JNITest * Method: getStringFromJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_ndk_JNITest_getStringFromJNI (JNIEnv *env, jobject object) { return (*env)->NewStringUTF(env, "hello from jni!"); } /* * Class: com_example_ndk_JNITest * Method: javaStringToCString * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaStringToCString (JNIEnv *env, jobject object, jstring javaString) { jboolean isCopy; const char *str = (*env)->GetStringUTFChars(env, javaString, &isCopy); if (str != 0) { syslog(LOG_INFO, "java string:%s", str); if (isCopy == JNI_TRUE) { syslog(LOG_INFO, "C string is a copy of the java string"); // 返回该结果 } else { syslog(LOG_INFO, "C string points to actual string"); } (*env)->ReleaseStringUTFChars(env, javaString, str); } } JNITest.java package com.example.ndk; public class JNITest { static { System.loadLibrary("ndk"); } public native String getStringFromJNI(); public native void javaStringToCString(String javaString); } JniActivity.java package com.example.ndk; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); JNITest jniTest = new JNITest(); TextView tvNdk = findViewById(R.id.tv_ndk); tvNdk.append("1.getStringFromJNI() = " + jniTest.getStringFromJNI()); tvNdk.append("\n"); jniTest.javaStringToCString("this is a java string"); } }

    2 数组操作

    2.1 创建数组

    通过 NewxxxArray 函数在原生代码中创建数组实例,xxx 表示具体的数组类型,比如Int为NewIntArray创建int类型的数组。

    jintArray javaArray; javaArray = (*env)->NewIntArray(env, 10); if (javaArray != 0) { // 处理数组 }

    2.2 访问数组元素

    JNI提供两种访问java数组元素的方法,可以将数组的代码复制成C数组,或者让JNI提供直接指向数组元素的指针。

    2.3 对副本操作

    对数组进行操作时需要注意,当数组很大时,如果可能的话,原生代码应该只获取或设置数组元素区域而不是获取整个数组(即复制java数组的部分元素或者复制C数组的部分元素),提高操作数组时的性能。

    2.3.1 将java数组复制到C数组中

    通过 GetxxxArrayRegion 函数将java数组复制到C数组中。

    jintArray javaArray; // java层传递到JNI的数组 jint nativeArray[10]; // 复制后的元素存储数组 (*env)->GetIntArrayRegion(env, javaArray, 0, 10, nativeArray);

    2.3.2 将C数组复制给java数组

    通过 SetxxxArrayRegion 函数将C数组复制给java数组中。

    jintArray javaArray[10] = (*env)->NewIntArray(env, 10); jint elements[10]; // 存放数组元素的数组 (*env)->SetIntArrayRegion(env, javaArray, 0, 10, elements);

    2.4 对直接指针操作(操作数组元素)

    通过 GetxxxArrayElements 函数获取指向java数组元素的直接指针。 通过 ReleasexxxArrayElements 函数释放元素指针。

    jint* nativeDirectArray; jboolean isCopy; // 第三个参数可选,表示让调用者确定返回的C字符串地址指向副本地址还是指向堆中的固定对象 nativeDirectArray = (*env)->GetIntArrayElements(env, javaArray, &isCopy); // 释放指向java数组元素的直接指针,防止内存泄漏 // 第四个参数是释放的模式: // 0:将内容复制回来并释放原生数组 // JNI_COMMIT:将内容复制回来但是不释放原生数组,一般用于周期性地更新一个java数组 // JNI_ABORT:释放原生数组但不用将内容复制回来 (*env)->ReleaseIntArrayElements(env, javaArray, nativeDirectArray, 0);

    2.5 数组操作demo

    com_example_ndk_JNITest.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_ndk_JNITest */ #ifndef _Included_com_example_ndk_JNITest #define _Included_com_example_ndk_JNITest #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_ndk_JNITest * Method: javaArrayCopyToCArray * Signature: ([I)V */ JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaArrayCopyToCArray (JNIEnv *, jobject, jintArray); /* * Class: com_example_ndk_JNITest * Method: cArrayCopyToJavaArray * Signature: ()[I */ JNIEXPORT jintArray JNICALL Java_com_example_ndk_JNITest_cArrayCopyToJavaArray (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif com_example_ndk_JNITest.c #include "/include/com_example_ndk_JNITest.h" #include <jni.h> #include <stdio.h> #include <syslog.h> /* * Class: com_example_ndk_JNITest * Method: javaArrayCopyToCArray * Signature: ([I)V */ JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaArrayCopyToCArray (JNIEnv *env, jobject object, jintArray javaArray) { // 获取数组长度 jint arrayLength = (*env)->GetArrayLength(env, javaArray); syslog(LOG_INFO, "java array length is %d", arrayLength); // 将java数组复制给native数组 jint nativeArray[arrayLength]; (*env)->GetIntArrayRegion(env, javaArray, 0, arrayLength, nativeArray); for (jint i = 0; i < arrayLength; i++) { syslog(LOG_INFO, "after copy show native element %d", nativeArray[i]); // 打印复制后的数组元素 } } /* * Class: com_example_ndk_JNITest * Method: cArrayCopyToJavaArray * Signature: ()[I */ JNIEXPORT jintArray JNICALL Java_com_example_ndk_JNITest_cArrayCopyToJavaArray (JNIEnv *env, jobject object) { // 创建一个返回给java的数组 jintArray javaArray = (*env)->NewIntArray(env, 10); jint elements[10]; for (jint i = 0; i < 10; i++) { elements[i] = i * 2; } // 将元素数组设置给java数组返回 (*env)->SetIntArrayRegion(env, javaArray, 0, 10, elements); return javaArray; } JNITest.java package com.example.ndk; public class JNITest { static { System.loadLibrary("ndk"); } public native void javaArrayCopyToCArray(int[] javaArray); public native int[] cArrayCopyToJavaArray(); } JniActivity.java package com.example.ndk; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); JNITest jniTest = new JNITest(); TextView tvNdk = findViewById(R.id.tv_ndk); tvNdk.append("2.javaArrayCopyToCArray()"); tvNdk.append("\n"); int[] array = new int[10]; for (int i = 0; i < 10; i++) { array[i] = i * 2; } jniTest.javaArrayCopyToCArray(array); int[] javaArray = jniTest.cArrayCopyToJavaArray(); if (javaArray != null) { tvNdk.append("3.cArrayCopyToJavaArray()"); tvNdk.append("\n"); for (int i = 0; i < javaArray.length; i++) { tvNdk.append("element[" + i + "] = " + javaArray[i]); tvNdk.append("\n"); } } } }

    3 NIO操作

    与数组操作相比,NIO缓冲区的数据传荣性能更好,更适合在原生代码和java应用程序之间传送大量数据。

    3.1 创建直接字节缓冲区

    unsigned char* buffer = (unsigned char*) malloc(1024); ... jobject directBuffer; directBuffer = (*env)->NewDirectByteBuffer(env, buffer, 1024);

    3.2 获取直接字节缓冲区

    unsigned char* buffer; buffer = (unsigned char*) (*env)->GetDirectBufferAddress(env, directBuffer);

    4 JNI访问java的成员变量和方法总结

    JNI无论访问成员变量还是方法,都需要经过三个步骤:

    获取实例clazz,通过JNI方法中的第二个函数参数jobject

    获取id

    获取java对象的成员变量和方法

    5 JNI访问java对象的成员变量

    java有两类域:实例域和静态域。类的每个实例都有自己的实例域副本,而一个类的所有实例共享同一个静态域。

    public class JavaClass { // 实例域 private String instanceField = "Instance Field"; // 静态域 private static String staticField = "Static Field"; }

    JNI中获取java对象的成员变量经过三个步骤:

    通过JNI函数中第二个参数jobject对象引用获取实例对象clazz

    根据clazz获取实例对象的成员变量id或静态成员变量id

    根据clazz和id获取实例对象的成员变量或静态成员变量

    注意:每次从JNI调用java都需要经过两到三个函数,这会导致性能上的下降。强烈建议将所有需要的参数传递给native方法,而不是通过JNI调回java。

    5.1 通过对象引用获取类

    // jobject对象引用是JNI函数第二个参数jobject获取 jclass clazz; clazz = (*env)->GetObjectClass(env, jobject);

    5.2 获取java对象的成员变量id

    // Ljava/lang/String:成员变量的具体类型 jfieldID instanceFieldId; instanceFiledId = (*env)->GetFieldID(env, clazz, "instanceField", "Ljava/lang/String;"); // 分号不能少

    5.3 获取java对象的静态成员变量id

    jfieldID staticFieldId; staticFieldId = (*env)->GetStaticFieldID(env, clazz, "staticFieldId", "Ljava/lang/String;"); // 分号不能少

    5.4 获取java对象的成员变量

    通过 GetxxxField 函数获取成员变量,比如GetIntField表示获取java对象中指定成员变量id的int类型的成员变量。

    jstring instanceField; instanceField = (*env)->GetObjectField(env, jobject, instanceFieldId);

    通过 GetStaticxxxField 函数获取静态成员变量。

    jstring staticField; staticField = (*env)->GetStaticObjectField(env, clazz, staticFieldId); // 注意第二个参数是clazz,不是jobject

    5.5 获取成员变量demo

    com_example_ndk_JNITest.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_ndk_JNITest */ #ifndef _Included_com_example_ndk_JNITest #define _Included_com_example_ndk_JNITest #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_ndk_JNITest * Method: javaField * Signature: ()V */ JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaField (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif com_example_ndk_JNITest.c #include "/include/com_example_ndk_JNITest.h" #include <jni.h> #include <stdio.h> #include <syslog.h> /* * Class: com_example_ndk_JNITest * Method: javaField * Signature: ()V */ JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_javaField (JNIEnv *env, jobject object) { // 获取java class jclass clazz = (*env)->GetObjectClass(env, object); // 获取class中实例域的id jfieldID instanceFieldId = (*env)->GetFieldID(env, clazz, "mInstanceField", "Ljava/lang/String;"); // 获取class中实例域成员变量 jstring instanceField = (*env)->GetObjectField(env, object, instanceFieldId); // 上面的是java字符串,要转换为native的字符串才可以使用,否则会闪退 const char *instanceFieldStr = (*env)->GetStringUTFChars(env, instanceField, NULL); syslog(LOG_INFO, "java instance field value %s", instanceFieldStr); (*env)->ReleaseStringUTFChars(env, instanceField, instanceFieldStr); // 获取class中静态域的id jfieldID staticFieldId = (*env)->GetStaticFieldID(env, clazz, "sStaticField", "Ljava/lang/String;"); // 获取class中静态域成员变量 jstring staticField = (*env)->GetStaticObjectField(env, clazz, staticFieldId); const char *staticFieldStr = (*env)->GetStringUTFChars(env, staticField, NULL); syslog(LOG_INFO, "java static field value %s", staticFieldStr); (*env)->ReleaseStringUTFChars(env, staticField, staticFieldStr); } JNITest.java package com.example.ndk; public class JNITest { // 实例域 private String mInstanceField = "instanceField"; // 静态域 private static String sStaticField = "staticField"; static { System.loadLibrary("ndk"); } public native void javaField(); } JniActivity.java package com.example.ndk; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); JNITest jniTest = new JNITest(); jniTest.javaField(); } }

    6 JNI调用java对象中的方法

    java中有两类方法:实例方法和静态方法。

    public class JavaClass { // 实例方法 private String instanceMethod() { return "Instance Method"; } // 静态方法 private static String staticMethod() { return "Static Method"; } }

    JNI中调用java对象方法经过三个步骤:

    通过JNI函数中第二个参数jobject对象引用获取实例对象

    根据clazz获取实例对象的方法id或静态方法id

    根据clazz和id调用实例对象的方法或静态方法

    6.1 获取实例方法的id

    // ()Ljava/lang/String:表示方法签名 jmethodID instanceMethodId; instanceMethodId = (*env)->GetMethodID(env, clazz, "instanceMethod", "()Ljava/lang/String;");

    6.2 获取静态方法的id

    jmethodID staticMethodId; staticMethodId = (*env)->GetStaticMethodID(env, clazz, "staticMethod", "()Ljava/lang/String;");

    6.3 调用实例方法

    通过 CallxxxMethod 函数调用实例的方法。

    jstring instanceMethodResut; instanceMethodResult = (*env)->CallStringMethod(env, jobject, instanceMethodId); // 注意这里是jobject

    6.4 调用静态方法

    通过 CallStaticxxxMethod 函数调用静态方法。

    jstring staticMethodResult; staticMethodResult = (*env)->CallStaticStringMethod(env, clazz, staticMethodId); // 注意这里是clazz

    6.5 调用方法demo

    com_example_ndk_JNITest.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_ndk_JNITest */ #ifndef _Included_com_example_ndk_JNITest #define _Included_com_example_ndk_JNITest #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_ndk_JNITest * Method: callMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_callMethod (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif com_example_ndk_JNITest.c #include "/include/com_example_ndk_JNITest.h" #include <jni.h> #include <stdio.h> #include <syslog.h> /* * Class: com_example_ndk_JNITest * Method: callMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_com_example_ndk_JNITest_callMethod (JNIEnv *env, jobject object) { // 获取java class jclass clazz = (*env)->GetObjectClass(env, object); // 获取class中实例方法的id jmethodID instanceMethodId = (*env)->GetMethodID(env, clazz, "instanceMethod", "()Ljava/lang/String;"); // 调用java中实例方法 jstring instanceMethodResult = (*env)->CallObjectMethod(env, object, instanceMethodId); const char *instanceMethodResultStr = (*env)->GetStringUTFChars(env, instanceMethodResult, NULL); syslog(LOG_INFO, "java instance method return value %s", instanceMethodResultStr); (*env)->ReleaseStringUTFChars(env, instanceMethodResult, instanceMethodResultStr); // 获取class中静态方法的id jmethodID staticMethodId = (*env)->GetStaticMethodID(env, clazz, "staticMethod", "()Ljava/lang/String;"); // 调用java中静态方法 jstring staticMethodResult = (*env)->CallStaticObjectMethod(env, clazz, staticMethodId); const char *staticMethodResultStr = (*env)->GetStringUTFChars(env, staticMethodResult, NULL); syslog(LOG_INFO, "java static method return value %s", staticMethodResultStr); (*env)->ReleaseStringUTFChars(env, staticMethodResult, staticMethodResultStr); } JNITest.java package com.example.ndk; public class JNITest { // 实例方法 private String instanceMethod() { return "instance method"; } // 静态方法 private static String staticMethod() { return "static method"; } static { System.loadLibrary("ndk"); } public native void callMethod(); } JniActivity.java package com.example.ndk; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); JNITest jniTest = new JNITest(); jniTest.callMethod(); } }

    7 域和方法描述符(JNI调用获取id时需要用到的类型签名声明)

    JNIJavaZjava中的boolean类型Bjava中的byte类型Cjava中的Char类型Sjava中的short类型Ijava中的int类型Jjava中的long类型Fjava中的float类型Djava中的double类型L全类名路径java中的对象类型,如 Ljava/lang/String;(注意分号不能丢)[typejava中的数组类型(arg-type)ret-typejava中的方法类型,例如 (III)V方法签名表示方法传递三个int类型参数,返回类型为void
    最新回复(0)