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

项目代码: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 本地通信

以下介绍的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时需要用到的类型签名声明)

JNIJava
Zjava中的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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
牙科就诊管理系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的Mysql数据库进行程序开发。实现了用户在线查看数据。管理员管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等功能。牙科就诊管理系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。 管理员在后台主要管理病例管理、字典管理、公告管理、药单管理、药品管理、药品收藏管理、药品评价管理、药品订单管理、牙医管理、牙医收藏管理、牙医评价管理、牙医挂号管理、用户管理、管理员管理等。 牙医列表页面,此页面提供给管理员的功能有:查看牙医、新增牙医、修改牙医、删除牙医等。公告信息管理页面提供的功能操作有:新增公告,修改公告,删除公告操作。公告类型管理页面显示所有公告类型,在此页面既可以让管理员添加新的公告信息类型,也能对已有的公告类型信息执行编辑更新,失效的公告类型信息也能让管理员快速删除。药品管理页面,此页面提供给管理员的功能有:新增药品,修改药品,删除药品。药品类型管理页面,此页面提供给管理员的功能有:新增药品类型,修改药品类型,删除药品类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值