NDK/JNI Develop Guide (4) How to use Array

NDK/JNI Develop Guide (4) How to use Array

JNI中的数组分为基本类型数组和对象数组,它们的处理方式是不一样的,基本类型数组中的所有元素都是JNI
的基本数据类型,可以直接访问。而对象数组中的所有元素是一个类的实例或其它数组的引用,和字符串操作
一样,不能直接访问Java传递给JNI层的数组,必须选择合适的JNI函数来访问和设置Java层的数组对象。阅
读此文假设你已经了解了JNI与Java数据类型的映射关系.下面以int类型为例说明基本数据类型数组的访问方
式,对象数组类型用一个创建二维数组的例子来演示如何访问:


public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("math");// load library to android project
    }

    private int[] array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    private TextView sumTxt;
    private TextView printArr;

    @SuppressLint("DefaultLocale")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sumTxt = (TextView) findViewById(R.id.sun_txt);
        printArr = (TextView) findViewById(R.id.print_arr);
        IntArray intArray = new IntArray();
        int sum = intArray.sumArray(array);
        Toast.makeText(this, "The Attays sum is " + sum, Toast.LENGTH_SHORT).show();
        sumTxt.setText("The Attays sum is " + sum);
        int[][] arr = intArray.initInt2DArray(3);
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < 3; i++) {//get the arrays and add those into builder
            for (int j = 0; j < 3; j++) {
                stringBuilder.append(String.format("arr[%d][%d] = %d\n",i,j,arr[i][j]));
                System.out.format("arr[%d][%d] = %d\n", i, j, arr[i][j]);
            }
        }
        printArr.setText(stringBuilder.toString());//show on the TextView
    }
}

下面开一下写好的两个本地的方法:

/**
 * Created by blueZhang on 16/6/28.
 *
 * @Author: BlueZhang
 * @date: 16/6/28
 */
public class IntArray {
    public native int sumArray(int[] arr);//caculate the sum of array from java 
    public native int[][] initInt2DArray(int size);//init the array and return the result
}

下面看一下我们的本地代码的实现:

//
// Created by blueZhang on 16/6/28.
//
#include "sum.h"
#include "stdlib.h"
#include "string.h"

/*
 * Class:     com_example_bluezhang_ndkarraytest_IntArray
 * Method:    sumArray
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_sumArray
        (JNIEnv *env, jobject obj, jintArray j_array) {
    jint i, sum = 0;
    jint *c_array;
    jint arr_len;
    //1. get the length of the array
    arr_len = (*env)->GetArrayLength(env, j_array);
    //2.get the buffer(java array element) from memory depend on data type
    c_array = (jint *) malloc(sizeof(jint) * arr_len);
    //3. Clear the cache area
    memset(c_array初始化缓冲区, 0, sizeof(jint) * arr_len);
    //4. Copy all elements in the Java array to the buffer
    (*env)->GetIntArrayRegion(env, j_array, 0, arr_len, c_array);
    for (i = 0; i < arr_len; i++) {
        sum += c_array[i];  //5. sum all elements
    }
    free(c_array);  //6. release the buffer
    return sum;
//      jint i, sum = 0;
//      jint *c_array;
//      jint arr_len;
//      // May be discontinuous in memory of the elements in an array, the JVM may copy all the raw data to the buffer, and then return a pointer to the buffer
//      c_array = (*env)->GetIntArrayElements(env,j_array,NULL);
//      if (c_array == NULL) {
//          return 0;   // copy into the buffer faliure
//      }
//      arr_len = (*env)->GetArrayLength(env,j_array);
//      printf("arr_len = %d\n", arr_len);
//      for (i = 0; i < arr_len; i++) {
//          sum += c_array[i];
//      }
//      (*env)->ReleaseIntArrayElements(env,j_array, c_array, 0); //release the buffer
//      return sum;
}

/*
 * Class:     com_example_bluezhang_ndkarraytest_IntArray
 * Method:    initInt2DArray
 * Signature: (I)[[I
 */
JNIEXPORT jobjectArray JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_initInt2DArray
        (JNIEnv *env, jobject obj, jint size)
{
    jobjectArray result;
    jclass clsIntArray;
    jint i,j;
    // 1.Get an int type 2 d array class reference
    clsIntArray = (*env)->FindClass(env,"[I");
    if (clsIntArray == NULL)
    {
        return NULL;
    }
    // 2.Create an array object (the inside clsIntArray in each element)
    result = (*env)->NewObjectArray(env,size,clsIntArray,NULL);
    if (result == NULL)
    {
        return NULL;
    }

    // 3.assignment for array element
    for (i = 0; i < size; ++i)
    {
        jint buff[256];
        jintArray intArr = (*env)->NewIntArray(env,size);
        if (intArr == NULL)
        {
            return NULL;
        }
        for (j = 0; j < size; j++)
        {
            buff[j] = i + j;
        }
        (*env)->SetIntArrayRegion(env,intArr, 0,size,buff);
        (*env)->SetObjectArrayElement(env,result, i, intArr);
        (*env)->DeleteLocalRef(env,intArr);
    }

    return result;
}

这里我们再次讲解下如何使用android studio生成.h 文件:

首先进入到我们的目录:
/Users/bluezhang/Desktop/NDKArrayTest/app/build/intermediates/classes/debug
使用命令:javah -jni packagename.classname
这样生成我们的.h文件其实可以添加参数指定生成的目录以及名称这里掠过。

上例中,在Java中定义了一个sumArray的native方法,参数类型是int[],对应JNI中jintArray类型。
在本地代码中,首先通过JNI的GetArrayLength函数获取数组的长度,已知数组是jintArray类型,可以得
出数组的元素类型是jint,然后根据数组的长度和数组元素类型,申请相应大小的缓冲区。如果缓冲区不大
的话,当然也可以直接在栈上申请内存,那样效率更高,但是没那么灵活,因为Java数组的大小变了,本地代
码也跟着修改。接着调用GetIntArrayRegion函数将Java数组中的所有元素拷贝到C缓冲区中,并累加数组
中所有元素的和,最后释放存储java数组元素的C缓冲区,并返回计算结果。GetIntArrayRegion函数第1
个参数是JNIEnv函数指针,第2个参数是Java数组对象,第3个参数是拷贝数组的开始索引,第4个参数是拷
贝数组的长度,第5个参数是拷贝目的地。在这里 我们的第一步的工作意境完成了,现在我们要做的就是配置自己的project的build.gradle 文件。生成.so 文件这样我们就可以在创建之后调用.so文件实现我们的目的也就是计
算array的和以及初始化一个array并且反馈给java代码这样就能使用生成的array进行展示。
在上面的代码中我们使用一种方式进行数据的计算下面的注释的代码是另一种实现的方式。有兴趣的可以自己手动实现
一下,查阅相关的资料。

小结
1、对于小量的、固定大小的数组,应该选择Get/SetArrayRegion函数来操作数组元素是效率最高的。因为这对
函数要求提前分配一个C临时缓冲区来存储数组元素,你可以直接在Stack(栈)上或用malloc在堆上来动态申请
,当然在栈上申请是最快的。有童鞋可能会认为,访问数组元素还需要将原始数据全部拷贝一份到临时缓冲区才能
访问而觉得效率低?我想告诉你的是,像这种复制少量数组元素的代价是很小的,几乎可以忽略。这对函数的另外
一个优点就是,允许你传入一个开始索引和长度来实现对子数组元素的访问和操作(SetArrayRegion函数可以修
改数组),不过传入的索引和长度不要越界,函数会进行检查,如果越界了会抛出ArrayIndexOutOfBoundsException异常。
2、如果不想预先分配C缓冲区,并且原始数组长度也不确定,而本地代码又不想在获取数组元素指针时被阻塞的话
,使用Get/ReleasePrimitiveArrayCritical函数对,就像Get/ReleaseStringCritical函数对一样,使
用这对函数要非常小心,以免死锁。
3、Get/Release<type>ArrayElements系列函数永远是安全的,JVM会选择性的返回一个指针,这个指针可能
指向原始数据,也可能指向原始数据的复制。

访问对象数组

我们可以看到刚刚生成的代码中存在两个JNI的借口:

/*
 * Class:     com_example_bluezhang_ndkarraytest_IntArray
 * Method:    sumArray
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_sumArray
  (JNIEnv *, jobject, jintArray);

/*
 * Class:     com_example_bluezhang_ndkarraytest_IntArray
 * Method:    initInt2DArray
 * Signature: (I)[[I
 */
JNIEXPORT jobjectArray JNICALL Java_com_example_bluezhang_ndkarraytest_IntArray_initInt2DArray
  (JNIEnv *, jobject, jint);

生成的第二个方法也就是我们要访问Java中的对象数组初始化成的数组为一个3*3的二维数组,数组元素的值为元素角标
的和并且将这个数组返回。
本地函数initInt2DArray首先调用JNI函数FindClass获得一个int型的二维数组类的引用,传递给
FindClass的参数”[I”是JNI class descript(JNI类型描述符,后面为详细介绍),它对应着JVM中
的int[]类型。如果int[]类加载失败的话,FindClass会返回NULL,然后抛出一个
java.lang.NoClassDefFoundError: [I异常。接下来,NewObjectArray创建一个新的数组,这个数
组里面的元素类型用intArrCls(int[])类型来表示。函数NewObjectArray只能分配第一维,JVM没有与
多维数组相对应的数据结构,JNI也没有提供类似的函数来创建二维数组。由于JNI中的二维数组直接操作的
是JVM中的数据结构,相比JAVA和C/C++创建二维数组要复杂很多。给二维数组设置数据的方式也非常直接,
首先用NewIntArray创建一个JNI的int数组,并为每个数组元素分配空间,然后用SetIntArrayRegion
把buff[]缓冲中的内容复制到新分配的一维数组中去,最后在外层循环中依次将int[]数组赋值到jobjectArray
数组中,一维数组中套一维数组,就形成了一个所谓的二维数组。另外,为了避免在循环内创
建大量的JNI局部引用,造成JNI引用表溢出,所以在外层循环中每次都要调用DeleteLocalRef将新创建
的jintArray引用从引用表中移除。在JNI中,只有jobject以及子类属于引用变量,会占用引用表的空间
,jint,jfloat,jboolean等都是基本类型变量,不会占用引用表空间,即不需要释放。引用表最大空间
为512个,如果超出这个范围,JVM就会挂掉。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值