一个专注音视频领域问答的小圈子

Android JNI 调用时的异常主要有如下两种:

  • Native 代码调用 Java 层代码时发生了异常要处理
  • Native 代码自己抛出了一个异常让 Java 层去处理

可以看到异常的发生和处理基本都需要 Native 和 Java 交互,而对于 Native 自身出了异常,也就是 C/C++ 代码有问题,导致应用崩溃的又是另一回事了。

Native 调用 Java 方法时的异常

之前的博客中就讲述了如何从 Native 调用 Java 的方法,先准备一个有异常的方法供 Native 去调用。

    private int operation() {
        return 2 / 0;
    }

然后在 Native 中调用该方法。

    jclass cls = env->FindClass("com/glumes/cppso/jnioperations/ExceptionOps");
    jmethodID mid = env->GetMethodID(cls, "<init>", "()V");
    jobject obj = env->NewObject(cls, mid);
    mid = env->GetMethodID(cls, "operation", "()I");
    // 先初始化一个类,然后调用类方法,就如博客中描述的那样
    env->CallIntMethod(obj, mid);

显然,除数为 0 ,一调用应用直接崩溃了。

 java.lang.ArithmeticException: divide by zero

接下来就是要进行异常处理的部分了。

    jclass cls = env->FindClass("com/glumes/cppso/jnioperations/ExceptionOps");
    jmethodID mid = env->GetMethodID(cls, "<init>", "()V");
    jobject obj = env->NewObject(cls, mid);
    mid = env->GetMethodID(cls, "operation", "()I");
// 先初始化一个类,然后调用类方法,就如博客中描述的那样
    env->CallIntMethod(obj, mid);
    //检查是否发生了异常
    jthrowable exc = env->ExceptionOccurred();
//    jboolean result = env->ExceptionCheck();

    if (exc) {
        // 打印异常日志
        env->ExceptionDescribe();
        // 这行代码才是关键不让应用崩溃的代码,
        env->ExceptionClear();
        // 发生异常了要记得释放资源
        env->DeleteLocalRef(cls);
        env->DeleteLocalRef(obj);
    }

添加如上代码进行处理后,应用并不会直接崩溃了,并且在 LogCat 中会看到对应的异常日志,这里面到了做了哪些操作呢?

Native 提供了 ExceptionOccurredExceptionCheck 方法来检测是否有异常发生,前者返回的是 jthrowable 类型,后者返回的是 jboolean 类型。

如果有异常,会通过 ExceptionDescribe 方法来打印异常信息,方便我们在 LogCat 中看到对应的信息。

ExceptionClear 方法则是关键的不会让应用直接崩溃的方法,类似于 Java 的 catch 捕获异常处理,它会消除这次异常。

这样就把由 Native 调用 Java 时的一个异常进行了处理,当处理完异常之后,别忘了释放对应的资源。

不过,我们这样仅仅是消除了这次异常,还应该让调用者有异常的发生,那么就需要通过 Native 来抛出一个异常告诉 Java 调用者了。

Native 抛出 Java 中的异常

有时在 Native 代码中进行一些操作,需要抛出异常到 Java ,交由上层去处理。

比如 Java 调用 Native 方法传递了某个参数,而这个参数有问题,那么 Native 就可以抛出异常让 Java 去处理这个参数异常的问题。

Native 抛出异常的代码大致都是相同的,可以抽出一个通用函数来:

void throwByName(JNIEnv *env, const char *name, const char *msg) {
    jclass cls = env->FindClass(name);
    if (cls != NULL) {
        env->ThrowNew(cls, msg);
    }
    env->DeleteLocalRef(cls);
}
// 调用抛出异常
extern "C"
JNIEXPORT void JNICALL
Java_com_glumes_cppso_jnioperations_ExceptionOps_nativeThrowException(JNIEnv *env, jobject instance) {
    throwByName(env, "java/lang/IllegalArgumentException", "native throw exception");
}

根据异常类型和异常信息来抛出异常。

而在 Java 中,就必须要用 try … catch… 来准备好捕获这次异常了。

        try {
            nativeThrowException();
        } catch (IllegalArgumentException e) {
            Log.e("NativeMethod", e.getMessage());
        }

异常处理小结

除了以上两种异常情况之外,还有一个特别常见的异常,就是当判断某个变量为 NULL 之后,执行立即返回的操作。

当发生异常时,一定要先处理异常,然后才能继续执行后面的步骤。如果不是需要立即返回的,那么就通过 ExceptionClear清除这次异常,然后在进行其他的处理。

对于在 Native 中发生了异常,需要让 Java 层去处理了,则在 Native 中抛出对应的异常,由 Java 层去捕获,比如在使用 ExceptionClear 清除了异常之后,就可以通过 throwNew 来抛出异常信息。

具体的异常处理方法和时机还是要看具体的使用场景,选择最合适的处理方法。

具体示例代码可参考我的 Github 项目 https://github.com/glumes/AndroidDevWithCpp,欢迎 Star。

知识星球

一个专注音视频领域问答的小圈子

公众号音视频开发进阶对应的知识星球,一个编程开发领域的专业圈子,贩卖知识和技巧!

※ 入群须知:了解该星球能提供的价值和帮助,在提问时务必阐述好背景,附带相关的信息。

iOS 用户可以加我微信 ezglumes 邀请你进星球,有疑问也可以加我微信咨询。

※ 星球内容:

基础教程:

在知识星球连载的干货教程,可以在专栏中找到,随着时间的推移,教程也会越来越多:

- 音视频基础概念
- WebRTC 入门教程及源码实践
- 播放器教程及源码实践
- OpenGL 和特效开发教程
- Vulkan 入门教程

部分内容可以在博客 https://glumes.com 中检索到,后面会在星球里持续更新.

干货分享:

涵盖了移动开发和音视频工程领域的绝大部分,从项目实战角度出发,提升能力,包括但不限于以下领域:

- Android/iOS 移动开发
- Camera 开发
- 短视频编辑 SDK 项目实践
- 在线直播和推流
- WebRTC 开发
- 播放器基础和提高
- OpenGL 图像渲染及特效开发
- C++ 基础和提高
- FFmpeg 使用和分析
- 干货资源和书籍分享

不止于技术方面的,各种 IT 新闻、茶余饭后、生活趣事也欢迎大家分享!!!

技术答疑解惑:

针对上述基础教程和干货分享的答疑,另外还有音视频和 IT 开发中的各种交流讨论。

- 基础知识点答疑
- 工业项目实践答疑
- 问题排查思路分析

一个 BUG 排查很久,不如来星球里提个问题,效率提升百倍。

求职和面试辅导:

一站式职场服务,每份工作都值得用心对待!!!

- 面试题和面试经验分享
- 简历修改和模拟面试
- 大厂内推和信息同步
- 职场经验分享
- 职业规划和发展分析

※ 星主和合伙人介绍

星主是公众号音视频开发进阶的作者,也是网站 https://glumes.com 的作者,曾参与过抖音、剪映等头部音视频 APP 底层 SDK 的开发。

合伙人也是在头条、快手从事音视频架构师的职位,具有多年的音视频开发经验,能力圈覆盖了音视频的绝大多数领域,资深音视频从业人员为你保驾护航。

微信公众号

扫描下面的二维码关注我的微信公众号《音视频开发进阶》,推送更多精彩内容!

添加我的微信 ezglumes 拉你入音视频与图形图像技术群一起交流学习~

wechat-account-qrcode

原创文章,转载请注明来源:    Android JNI 调用时的异常处理