NDK(四)——JNI中数组、引用和异常的处理

NDK开发(三)——C/C++代码如何调用java层代码中介绍了如何在native层调用Java层的代码。这次我们接着上一讲的,继续讲解一下JNI中对数组、引用和异常的处理方式。

 一、数组的处理

 1.1传入数组

这一部分主要讲述的是我们怎么样给Java层的native函数传一个数组类型的参数,之后在native层利用C/C++对在Java层传入的这个数组进行相应的操作(如排序等)。直接上代码,看Java层的具体写法:

```
public class MainActivity extends AppCompatActivity {
    //加载动态库
    static {
        System.loadLibrary("native-lib");
    }
    private TextView tv;
    //定义好的数组
    int[]  array ={1,9,2,3,4,6,4,3,7};
    //参数类型为int数组的Java层native函数
    public native void giveArray(int[] array);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.sample_text);
        //调用native函数
        giveArray(array);
        //调用native函数后,在native层利用C语言对上面定义好的数组重新排序,之后打印出来
        for(int i : array){
            System.out.println(i);
        }
    }
}

```

native层:

```
#include <jni.h>
#include<stdlib.h>
//这个我们自定义的函数,在利用C语言的qsort函数进行排序时会用到
int compare(int *a,int *b){
    return (*a)-(*b);
}
JNIEXPORT void
JNICALL
Java_com_example_zhangxudong_ndkdemo_MainActivity_giveArray(
        JNIEnv *env,
        jobject obj,jintArray arr) {//这里多了一个jintArray类型的参数,这个参数和我们在Java层native函数中传入的参数是对应的
    jint *elems = (*env)->GetIntArrayElements(env, arr, NULL);
    //输出的两个数组地址不一样,这说明通过上面的GetIntArrayElements()函数,把原来的数组拷贝了一份
    printf("%#x,%#x\n", &elems, &arr);

    //数组的长度
    int len = (*env)->GetArrayLength(env, arr);
    //C语言中的排序函数,记得要引入stdlib.h头文件才可以使用
    qsort(elems, len, sizeof(jint), compare);
    //同步
    //最后一个参数有如下几种,代表的意义如下
    //0, Java数组进行更新,并且释放C/C++数组
    //JNI_ABORT, Java数组不进行更新,但是释放C/C++数组
    //JNI_COMMIT,Java数组进行更新,不释放C/C++数组(函数执行完,数组还是会释放)
    (*env)->ReleaseIntArrayElements(env, arr, elems, JNI_COMMIT);
}
```

运行程序,我们在AS中可以看到有如下输出,可以看到确实在native层进行了排序

```
4-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 1
04-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 2
04-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 3
04-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 3
04-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 4
04-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 4
04-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 6
04-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 7
04-07 03:08:43.749 2772-2772/com.example.zhangxudong.ndkdemo I/System.out: 9
```

1.2返回数组

这个当然指的就是在native层生成一个数组然后返回给Java层。

Java层代码:

```
public class MainActivity extends AppCompatActivity {
    //加载动态库
    static {
        System.loadLibrary("native-lib");
    }
    private TextView tv;

    public native int[] getArray(int len);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.sample_text);
        //调用native函数,得到native层创建的指定长度的数组
        int[] array = getArray(10);
        //打印得到的数组
        for(int i : array){
            System.out.println(i);
        }
    }
}
```

native层代码:

```
#include <jni.h>

JNIEXPORT jarray//注意这里的返回类型变为了数组类型
JNICALL
Java_com_example_zhangxudong_ndkdemo_MainActivity_getArray(
        JNIEnv *env,
        jobject obj,jint len) {
    //创建一个指定大小的数组
    jintArray jint_arr = (*env)->NewIntArray(env, len);
    jint *elems = (*env)->GetIntArrayElements(env, jint_arr, NULL);
    //初始化数组
    int i = 0;
    for (; i < len; i++){
        elems[i] = i;
    }
    //同步
    (*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0);
    return jint_arr;
}
```

最后的输出结果:

```
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 0
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 1
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 2
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 3
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 4
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 5
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 6
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 7
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 8
04-07 04:13:27.979 4747-4747/com.example.zhangxudong.ndkdemo I/System.out: 9
```
二、JNI引用变量

这里的引用类型分为“局部引用”和“全局引用”。对于局部引用变量,我们在用完之后要及时的进行释放,局部引用通过DeleteLocalRef手动释放对象;而对于全局引用变量则涉及到创建、获取和释放。

2.1局部引用

我们在这里简单的模拟一下:循环创建对象实例

```
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_localRef(JNIEnv *env, jobject jobj){
	int i = 0;
	for (; i < 5; i++){
		//创建Date对象
		jclass cls = (*env)->FindClass(env, "java/util/Date");
		jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
		jobject obj = (*env)->NewObject(env, cls, constructor_mid);
		//此处省略一百行代码...

		//不再使用jobject对象了
		//通知垃圾回收器回收这些对象
		(*env)->DeleteLocalRef(env, obj);
		//此处省略一百行代码...
	}
}
```

2.2全局引用

全局引用的创建通过NewGlobalRef()函数,释放则通过DeleteGlobalRef()函数

```
//创建
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_createGlobalRef(JNIEnv *env, jobject jobj){
	jstring obj = (*env)->NewStringUTF(env, "jni development is powerful!");
	global_str = (*env)->NewGlobalRef(env, obj);
}

//获得
JNIEXPORT jstring JNICALL Java_com_dongnaoedu_jni_JniTest_getGlobalRef(JNIEnv *env, jobject jobj){
	return global_str;
}

//释放
JNIEXPORT void JNICALL Java_com_dongnaoedu_jni_JniTest_deleteGlobalRef(JNIEnv *env, jobject jobj){
	(*env)->DeleteGlobalRef(env, global_str);
}
```

2.3弱全局引用

弱全局引用相对而言比较节省内存,在内存不足时可以释放所引用的对象。可以引用一个不常用的对象,如果引用的这个对象为NULL时,会临时创建。
创建:NewWeakGlobalRef,销毁:DeleteGlobalWeakRef。


三、异常处理
jni中发生异常时,抛出的是Throwable异常,我们在Java代码中通过try...catch是捕获不到的(Exeception,但用Throwable可以捕获到)。那我们要怎样处理native层发生的异常呢?具体我们还是通过代码来看吧。假如我们的代码在Java层和native层分别写成这样:

Java,

```
public class MainActivity extends AppCompatActivity {
    //加载动态库
    static {
        System.loadLibrary("native-lib");
    }

    public String key = "shadow";
    public native void exeception();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //jni中发生异常时,抛出的是Throwable异常,我们在Java代码中通过try...catch是捕获不到的
        //所以catch下面的那个输出语句是执行不到的
        try {
            //Java中的捕获语句是捕获不了jni的异常的	,所以这个try..catch语句不管用
             exeception();
        } catch (Exception e) {//改为Throwable e则可以捕获到
            System.out.println("发生异常:"+e.getMessage());
        }
        //如果发生了异常,下面的这句代码也执行不到
        System.out.println("--------异常发生之后-------");
    }
}

```

native层

```
JNIEXPORT void
JNICALL
Java_com_example_zhangxudong_ndkdemo_MainActivity_exeception(
        JNIEnv *env,
        jobject obj) {
        jclass cls = (*env)->GetObjectClass(env, obj);
        //因为我们的Java类中并没有名称为key2的这个属性(有key属性),所以这里肯定会发生异常
        jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;");

```
像这样去写Java层和native层的代码的话,那么我们在native层因为找不到对应的属性而发生异常的时候,对应的Java层因为其try...catch捕获不到native层的异常可能就会直接崩掉。那么我们应该怎样处理这个问题呢? 即如何在jni代码发生异常的情况下,保证上面Java代码任然能够正常执行呢?这就需要:
    1.对jni层的异常做相应的处理;
    2.异常发生并处理后再做相应的“补救措施”。
修改上面native层代码如下:
```
#include <jni.h>
#include <string.h>

JNIEXPORT void
JNICALL
Java_com_example_zhangxudong_ndkdemo_MainActivity_exeception(
        JNIEnv *env,
        jobject obj) {
        jclass cls = (*env)->GetObjectClass(env, obj);
        jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;");
        //检测是否发生Java异常
        jthrowable exception = (*env)->ExceptionOccurred(env);
        if (exception != NULL){
            //让Java代码可以继续运行
            //清空异常信息
            (*env)->ExceptionClear(env);
            //补救措施
            fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
        }
        //获取正确的属性的值
        jstring jstr = (*env)->GetObjectField(env, obj, fid);
        char *str = (*env)->GetStringUTFChars(env, jstr, NULL);
        //对比属性值是否合法,strcmp就是C语言中的一个比较两个字符串的函数是否相等的函数,相等返回0,在string.h头文件中
        //注意这里我们在Java层定义的key是“shadow”,而这里是和“super shadow”比较,所以肯下面的代码会执行到
        if (strcmp(str, "super shadow") != 0){
            //认为抛出异常,给Java层处理。注意:这两句很关键。
            jclass newExcCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
            (*env)->ThrowNew(env,newExcCls,"key's value is invalid!");
    }
}
```

Java层代码不变化:

```
public class MainActivity extends AppCompatActivity {
    //加载动态库
    static {
        System.loadLibrary("native-lib");
    }

    public String key = "shadow";
    public native void exeception();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
             exeception();
        } catch (Exception e) {
            //输出异常信息:发生异常:key's value is invalid!
            System.out.println("发生异常:"+e.getMessage());
        }
        //下面的输出代码可以执行到
        System.out.println("--------异常发生之后-------");
    }
}

```

通过上面对native层代码的修改,即使native层发生异常我们也可以在Java层通过try...catch捕获到,从而可以让代码顺利的执行下去。

```
04-15 03:15:06.134 4528-4528/com.example.zhangxudong.ndkdemo I/System.out: 发生异常:key's value is invalid!
04-15 03:15:06.134 4528-4528/com.example.zhangxudong.ndkdemo I/System.out: --------异常发生之后-------
```
通过上面在AS中打印出来的log可以清楚的看到native层的异常确实被我们Java层的catch语句块捕获到了,并且下面的代码也顺利的执行到了。





猜你喜欢

转载自blog.csdn.net/Hi_Red_Beetle/article/details/79841905