在 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弱全局引用
三、异常处理
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代码任然能够正常执行呢?这就需要:
```
#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语句块捕获到了,并且下面的代码也顺利的执行到了。