Android JNI和NDK学习(五):JNI调用数组

2,198 阅读7分钟

概述

今天我们继续学习JNI数组,此篇文章仅作为笔记,以防以后忘记

数组

JNI把java类型分为俩类,基本数据类型和引用数据类型,引用数据类型统一用jobject来表示,数组也一样,也分为基本数据类型和引用数据类型,引用数据类型为jobjectarray来表示

基本数据类型的数组

我们先来分析一下基本数据类型的数组相关的API

Get< PrimitiveType >ArrayElements

返回一个基本数据类型的数组,其中PrimitiveType指的是基本数据类型,比如你要获取int的数据类型的数组,那么PrimitiveType就是int

NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env,
                        ArrayType array, jboolean *isCopy);

  • ArrayType array:表示java的数组对象
  • jboolean *isCopy:*isCopy 为JNI_TRUE时,指针指向原数组的拷贝,*isCop为JNI_FALSE时,指针指向原数组

如果函数指针指向原数组,那么所有的修改都是在原数组上进行的,如果函数指针指向拷贝数组,那么所有的修改都是在拷贝数组上进行的,元素组不受影响

我么你需要保证当拷贝发生了,修改的数据可以同步到原数组,Release<PrimitiveType>ArrayElements就是这个作用

Release< PrimitiveType >ArrayElements

void Release<PrimitiveType>ArrayElements(JNIEnv *env,
                ArrayType array, NativeType *elems, jint mode);
  • ArrayType array:原数组java对象
  • NativeType *elems:JNI数组类型的指针,本地数组
  • jint mode:0表示把修改同步到原数组,并释放本地数组 JNI_COMMIT:把修改同步到原数组,但是不释放本地数组 JNI_ABORT:不把修改同步到原数组,但是释放本地数组

当Get< PrimitiveType >ArrayElements函数不发生拷贝,那么mode不起任何作用

实战

java代码

public class TextJniArray {

    static {
        System.loadLibrary("textjniarray");
    }


    public native void textArray(int[] array);


}

C代码

#include <jni.h>
#include <android/log.h>

static void native_text_array(JNIEnv *env, jobject jobject1, jintArray array) {

    //获取数组长度
    jsize length = env->GetArrayLength(array);
    //获取本地数组
    jint *native_intaray = env->GetIntArrayElements(array, NULL);
    //操作本地数组
    for(int i=0;i<length;i++){
        native_intaray[i]+=100;
    }
    //释放本地数组
    env->ReleaseIntArrayElements(array,native_intaray,0);
}


static const JNINativeMethod nativeMethod[] = {
        {"textArray", "([I)V", (void *) native_text_array}
};

static int registNativeMethod(JNIEnv *env) {
    int result = -1;

    jclass class_text = env->FindClass("com.text.ndk1.TextJniArray");
    if (env->RegisterNatives(class_text, nativeMethod,
                             sizeof(nativeMethod) / sizeof(nativeMethod[0])) == JNI_OK) {
        result = 0;
    }
    return result;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    int result = -1;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_1) == JNI_OK) {
        if (registNativeMethod(env) == JNI_OK) {
            result = JNI_VERSION_1_6;
        }
        return result;
    }
}

调用

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int[] a = {0, 1, 2, 3, 4, 5};
        for (int i = 0; i < 6; i++) {
            Log.d("mmm调用前数组数据", a[i] + "/");
        }
        new TextJniArray().textArray(a);

        for (int i = 0; i < 6; i++) {
            Log.d("mmm调用后数组数据", a[i] + "/");
        }
    }

打印

D/mmm调用前数组数据: 0/
D/mmm调用前数组数据: 1/
D/mmm调用前数组数据: 2/
D/mmm调用前数组数据: 3/
D/mmm调用前数组数据: 4/
D/mmm调用前数组数据: 5/

D/mmm调用后数组数据: 100/
D/mmm调用后数组数据: 101/
D/mmm调用后数组数据: 102/
D/mmm调用后数组数据: 103/
D/mmm调用后数组数据: 104/
D/mmm调用后数组数据: 105/

Get< PrimitiveType >ArrayRegion

void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
                                jsize start, jsize len, NativeType *buf);

  • ArrayType array:java数组对象
  • jsize start:拷贝开始位置
  • jsize len:拷贝的长度
  • NativeType *buf:本地缓存数组

这个函数的作用是拷贝java数组ArrayType array到本地数组NativeType *buf,如果在本地数组上修改,需要同步到java数组,那么需要调用Set<PrimitiveType>ArrayRegion

Set< PrimitiveType >ArrayRegion

void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
            jsize start, jsize len, const NativeType *buf);
  • ArrayType array:java原始数组
  • jsize start:开始索引
  • jsize len:拷贝长度
  • const NativeType *buf:本地缓存数组

这个函数的作用是把本地数组NativeType *buf数据写回到java原数组中,起始点为start,长度为len

实战

这次只贴C代码

static void native_text_array1(JNIEnv *env, jobject jobject1, jintArray array) {

    //获取数组长度
    jsize length = env->GetArrayLength(array);
    //创建本地缓存数组
    jint native_array[length - 2];
    //进行数组考别
    env->GetIntArrayRegion(array, 2, length - 2, native_array);

    for (int i = 0; i < length - 2; ++i) {
        native_array[i] += 100;
    }
    //把修改数据写回原数组
    env->SetIntArrayRegion(array, 2, length - 2, native_array);

}

打印

D/mmm调用前数组数据: 0/
D/mmm调用前数组数据: 1/
D/mmm调用前数组数据: 2/
D/mmm调用前数组数据: 3/
D/mmm调用前数组数据: 4/
D/mmm调用前数组数据: 5/

D/mmm调用后数组数据: 0/
D/mmm调用后数组数据: 1/
D/mmm调用后数组数据: 102/
D/mmm调用后数组数据: 103/
D/mmm调用后数组数据: 104/
D/mmm调用后数组数据: 105/

New< PrimitiveType >Array

这个方法可以在JNI层创建数组,然后返回给java

实战

C代码

static jintArray native_text_array2(JNIEnv *env, jobject jobject1) {
    jintArray array = env->NewIntArray(2);

    jint *native_array = env->GetIntArrayElements(array, NULL);

    for (int i = 0; i < 2; ++i) {
        native_array[i] = 100 + i;
    }

    env->ReleaseIntArrayElements(array, native_array, 0);

    return array;
}

调用

 int[] a = new TextJniArray().textArray2();

        for (int i = 0; i < 2; i++) {
            Log.d("mmm数组数据", a[i] + "/");
        }

打印

D/mmm数组数据: 100/
D/mmm数组数据: 101/

引用数据类型的数组

先看下需要用的API

GetObjectArrayElement

jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);

获取数组array在索引index下的元素

IsInstanceOf

jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);

用于判断对象obj,是不是class类的实例对象

实战

准备引用数据类型

public class Person {
    public String name;

    public Person(String name) {
        this.name = name;
    }

    public void say() {
        Log.d("mmm", name + "在说话");
    }
}

准备native方法

public native void textArray3(Person[] persons);

准备C实现

static void native_text_array3(JNIEnv *env, jobject jobject1, jobjectArray jobjectArray1) {

    //获取数组长度
    jsize length = env->GetArrayLength(jobjectArray1);
    //获取person的class对象
    jclass jclass_person = env->FindClass("com.text.ndk1.Person");

    if (jclass_person == NULL) {
        return;
    }
    //获取方法
    jmethodID jmethodId_say = env->GetMethodID(jclass_person, "say", "()V");

    if (jmethodId_say == NULL) {
        return;
    }

    for (int i = 0; i < length; ++i) {
        //获取java引用数组中的元素
        jobject jobject2 = env->GetObjectArrayElement(jobjectArray1, i);
        //判断元素的类型
        if (env->IsInstanceOf(jobject2, jclass_person)) {
            //调用元素的方法
            env->CallVoidMethod(jobject2, jmethodId_say);
        }
    }

}

调用

        Person[] people = new Person[2];
        people[0] = new Person("jj");
        people[1] = new Person("oj");
        new TextJniArray().textArray3(people);

打印数据

D/mmm: jj在说话
D/mmm: oj在说话

NewObjectArray

jobjectArray NewObjectArray(JNIEnv *env, jsize length,
                        jclass elementClass, jobject initialElement);

作用是在JNI层创建引用微数据类型数组,NewObjectArray函数会根据elementClass类型创建一个长度length的引用数据类型数组

如果指定第四个参数则会初始化数组,如果指定为NULL,则数组的所有元素为null

SetObjectArrayElement

void SetObjectArrayElement(JNIEnv *env, jobjectArray array, 
                        jsize index, jobject value);

为数组array,设置索引index下的值为value

NewObject & NewObjectA & NewObjectV

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

这三个参数区别就是传入的参数不一样,但是作用一样

  • 参数class代表,java的class对象,可以通过FindClass来寻找
  • 参数methodID指的是java的构造函数,通过GetMethodID来获取,传入的参数名为<init>,参数名的返回值为V

AllocObject

jobject AllocObject(JNIEnv *env, jclass clazz);

这个函数会给clazz类的对象分配内存,而不用调用class类的构造函数,并返回一个执向这个对象的引用

实战

准备java的native方法

public native Person[] textArray4(int length);

准备C实现

static jobjectArray native_text_array4(JNIEnv *env, jobject jobject1, jint length) {

    jclass jclass_person = env->FindClass("com.text.ndk1.Person");

    if (jclass_person == NULL) {
        return NULL;
    }

    //获取person的构造方法
    jmethodID jmethodId_gouzao = env->GetMethodID(jclass_person, "<init>", "(Ljava/lang/String;)V");

    if (jmethodId_gouzao == NULL) {
        return NULL;
    }

    //创建引用数组
    jobjectArray array_person = env->NewObjectArray(length, jclass_person, NULL);

    for (int i = 0; i < length; ++i) {
        jobject person = NULL;
        if (i == 0) {
            //构造方法创建对象
            person = env->NewObject(jclass_person, jmethodId_gouzao, env->NewStringUTF("小红"));
        }

        if (i == 1) {
            person = env->AllocObject(jclass_person);
            //直接分配内存
            jmethodID setname = env->GetMethodID(jclass_person, "setName", "(Ljava/lang/String;)V");
            env->CallVoidMethod(person, setname, env->NewStringUTF("小明"));
        }

        //把初始化好的对象赋值给数组
        env->SetObjectArrayElement(array_person, i, person);
        //释放局部变量
        env->DeleteLocalRef(person);
    }

    return array_person;

}

调用

  Person[] people1 = new TextJniArray().textArray4(2);

        for (int i = 0; i < people1.length; i++) {
            people1[i].say();
        }

打印数据

 D/mmm: 小红在说话
 D/mmm: 小明在说话

参考

juejin.cn/post/684490…