Android ndk-jni语法—— 5

259 阅读4分钟

这节主要学习jni中的引用,C中必须告诉java什么时候你可以回收,什么时候不能够回收,所以在jni中引入了引用。 引用类型分为:局部引用和全局引用。

一.局部引用

回收规则:局部引用会在C代码执行完毕之后自动回收释放。

问题:有时候我们需要自己手动去回收释放?

场景:创建对象数组。

创建native方法:

public native void localRef(); 

在C中去实现该方法,我们模拟创建对象数组,并手动释放的逻辑:

extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_MainActivity_localRef(JNIEnv *env, jobject jobj) {
    // 获取一个类对象 java.lang.String
    jclass  cls_str = env->FindClass("java/lang/String");
    // 获取构造方法(实例化)
    jmethodID mid = env->GetMethodID(cls_str, "<init>", "()V");
    for (int i = 0; i < 10; ++i) {
        // 创建对象
        jobject obj_str = env->NewObject(cls_str, mid);
        // 创建数组
        jobjectArray arr_obj = env->NewObjectArray(6, cls_str, obj_str);

        // 中间其他逻辑,省略...

        // 手动释放数组
        env->DeleteLocalRef(arr_obj);
        env->DeleteLocalRef(obj_str);
    }
}

注:局部引用不能在多线程中传递,也不能在其他方法中调用。

二.全局引用

可以在多线程中传递,手动释放之前一直有效,也能在其他方法中调用。我们同样创建两个native方法:

public native void setGlobalRef(); // 创建全局引用

public native String getGlobalRef(); // 获取全局引用

在C中实现两个方法:

jstring global_ref = nullptr; // 全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_MainActivity_setGlobalRef(JNIEnv *env, jobject jobj) {
    // 创建字符串
    jstring str = env->NewStringUTF("Hello NDK");
    if (str == nullptr) {
        return;
    }
    // 创建一个全局引用
    global_ref = (jstring)env->NewGlobalRef(str);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_carey_myndk_MainActivity_getGlobalRef(JNIEnv *env, jobject jobj) {
    return global_ref;
}

我们在java中调用下:

setGlobalRef();

String globalRef = getGlobalRef();
Log.e("carey", "==== globalRef:" + globalRef);

查看日志打印:

image.png

我们增加一个方法,去释放这个全局引用:

public native void releaseGlobalRef();

并实现这个方法:

extern "C"
JNIEXPORT void JNICALL
Java_com_carey_myndk_MainActivity_releaseGlobalRef(JNIEnv *env, jobject thiz) {
    env->DeleteGlobalRef(global_ref);
}

我们获取引用后,进行释放,睡眠1s后再次获取这个引用,看看打印结果:

String globalRef = getGlobalRef();
Log.e("carey", "==== globalRef:" + globalRef);

releaseGlobalRef();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}
String globalRefEnd = getGlobalRef();
Log.e("carey", "==== globalRef释放后:" + globalRefEnd);

image.png

我们能够看到,这个全局引用被释放了。

三.异常处理

我们声明一个native方法:

public native int optionException();

在方法实现里,我们调用一个不存在的方法:

extern "C"
JNIEXPORT jint JNICALL
Java_com_carey_myndk_MainActivity_optionException(JNIEnv *env, jobject thiz) {
    // 获取java中的类
    jclass ndk_obj = env->GetObjectClass(thiz);
    // 获取方法对象
    jmethodID  mid = env->GetMethodID(ndk_obj, "getIntRandom1", "(I)I");
    // 执行方法
    jint result_int = env->CallIntMethod(thiz, mid, 100);
    return result_int;
}

调用方法后会崩溃,方法找不到,打印日志如下:

image.png

我们改造上面的方法进行异常捕获处理:

extern "C"
JNIEXPORT jint JNICALL
Java_com_carey_myndk_MainActivity_optionException(JNIEnv *env, jobject thiz) {
    // 获取java中的类
    jclass ndk_obj = env->GetObjectClass(thiz);
    // 获取方法对象
    jmethodID mid = env->GetMethodID(ndk_obj, "getIntRandom1", "(I)I");

    // 处理异常,检测发生了异常,进行处理
    jthrowable exception = env->ExceptionOccurred();
    if (exception) {
        // 清空异常,否则java程序无法执行
        env->ExceptionClear();
        // 处理异常,调用存在的方法
        mid = env->GetMethodID(ndk_obj, "getRandomInt", "(I)I");
    }
    // 执行方法
    jint result_int = env->CallIntMethod(thiz, mid, 100);
    return result_int;
}

上面代码我们进行了异常捕获处理,同时调用了正确存在的方法,并且返回这个结果,我们在java中进行调用打印:

int result = optionException();
Log.e("carey", "==== 处理异常result:" + result);

查看打印:

image.png

我们也能看到程序不会崩溃,并正常运行。

我们看看怎么在java中去catch C中异常,先声明native方法,并实现:

public native int catchException();
extern "C"
JNIEXPORT jint JNICALL
Java_com_carey_myndk_MainActivity_catchException(JNIEnv *env, jobject thiz) {
    int a = 100;
    int b = 200;
    if (a != b) {
        jclass cls = env->FindClass("java/lang/IllegalArgumentException");
        env->ThrowNew(cls, "IllegalArgumentException!");
        return -1;
    }
    return 0;
}

这里主动去抛出一个异常,在java中去捕获,并打印:

try {
    int result = catchException();
    Log.e("carey", "==== 异常捕获result:" + result);
} catch (Exception e) {
    Log.e("carey", "==== 异常捕获e:" + e.getMessage());
}

查看打印:

image.png

四.总结

今天主要介绍了局部和全局引用,还有异常的捕获处理。喜欢的点赞收藏,感谢!