人间观察

做个俗人,得之坦然,失之淡然,顺其自然吧!

假期ing,~~~

今天我们讲Android JNI下的异常处理,在java中有异常处理机制,在jni中也一样。

回顾java异常

我们知道在java中分为运行期异常和编译期异常。

运行期异常时是程序在执行期间发生的异常,如果没有捕获可能导致程序不正常(轻者可能功能不正常,重则程序直接crash )。

编译期异常是代码编译期间必须显示捕获的异常。throws Exception {} ,try{} catch{} finally{}

今天我们不讲这个,如果不了解,可以网上找找&study

主角-JNI异常

在jni中native层代码执行不受jvm虚拟机的控制,因此有异常了并不会停止native函数的执行并把控制权交给异常处理程序。所以我们需要异常的检测和处理机制,一旦检查到异常时,原生native函数应该善后,比如:内存释放,返回合适的返回值,抛出异常给java层等

在jni中一般可以分为两种,一种是在native方法调用java层方法,java层方法出现了异常(crash等)。另一种是在native方法中出现了异常(传入参数不对,程序执行非预期)对外抛出java识别的java异常(Exception类或者它的子类)。

1.native方法调用java层方法出现异常&如何检测处理(crash)

我们先看一下如下代码

// JNIException.java
public class JNIException {static {System.loadLibrary("native-lib");}public native void nativeInvokeJavaException();public int getStingLen() {String s = null;return s.length();}
}

然后我们在JNI的nativeInvokeJavaException方法里调用

// jni_exception.cpp
extern "C" JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_exception_JNIException_nativeInvokeJavaException(JNIEnv *env, jobject thiz) {jclass cls = env->FindClass("com/bj/gxz/jniapp/exception/JNIException");jmethodID getStingLenMid = env->GetMethodID(cls, "getStingLen", "()I");jmethodID constructMid = env->GetMethodID(cls, "<init>", "()V");// new一个java对象jobject obj = env->NewObject(cls, constructMid);LOG_D("getStingLen start invoke");env->CallIntMethod(obj, getStingLenMid);LOG_D("getStingLen invoke done");
}

结果:如你所想s.length()发生NPE。APP直接crash了, 输出了如下日志

2020-10-3 10:57:11.333 7012-7012/com.bj.gxz.jniapp D/JNI: getStingLen start invoke
2020-10-3 10:57:11.333 7012-7012/com.bj.gxz.jniapp D/JNI: getStingLen invoke done
2020-10-3 10:57:11.334 7012-7012/com.bj.gxz.jniapp D/AndroidRuntime: Shutting down VMCaused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object referenceat com.bj.gxz.jniapp.exception.JNIException.getStingLen(JNIException.java:17)at com.bj.gxz.jniapp.exception.JNIException.nativeInvokeJavaException(Native Method)at com.bj.gxz.jniapp.MainActivity.onJniException(MainActivity.java:54)

但是,但是,它打印了getStingLen invoke done这行日志,也就是说如果在native层中调用java方法的时候,当java方法发生异常(crash)的时候native方法会继续执行完成(native代码执行不受java虚拟机的控制)。继续执行后面的代码可能就不是我们的预期了(因为我们需要之前方法的结果)。那怎么处理呢?我们的APP至少不能crash吧。

处理方法:
在jni中提供了ExceptionCheck,ExceptionOccurred 和ExceptionDescribe和ExceptionClear方法。

备注,里面有几个方法介绍
官方文档

这里就是直接说了。
ExceptionCheckExceptionOccurred 作用一样,都是检查是否发生了异常。
区别在于
ExceptionCheck:有异常返回JNI_TRUE,否则返回JNI_FALSE

ExceptionOccurred:用异常返回该异常的引用,否则返回NULL

ExceptionDescribe:打印异常的堆栈信息
ExceptionClear:清除异常堆栈信息

ok,我们再改造下实现,让它不crash。

extern "C" JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_exception_JNIException_nativeInvokeJavaException(JNIEnv *env, jobject thiz) {jclass cls = env->FindClass("com/bj/gxz/jniapp/exception/JNIException");jmethodID getStingLenMid = env->GetMethodID(cls, "getStingLen", "()I");jmethodID constructMid = env->GetMethodID(cls, "<init>", "()V");// new一个java对象jobject obj = env->NewObject(cls, constructMid);LOG_D("getStingLen start invoke");env->CallIntMethod(obj, getStingLenMid);LOG_D("getStingLen invoke done");// ExceptionOccurred作用和ExceptionCheck一样// jthrowable throwable = env->ExceptionOccurred();// 检查JNi调用是否发生了异常jboolean ret = env->ExceptionCheck();LOG_D("ExceptionCheck %d", ret);if (JNI_TRUE == ret) {// 打印Java层抛出的异常堆栈信息env->ExceptionDescribe();// 清除异常信息env->ExceptionClear();// ...这里可以处理异常发生逻辑return;}LOG_D("nativeInvokeJavaException exec end");
}

再次运行

2020-10-3 11:10:16.064 8925-8925/com.bj.gxz.jniapp D/JNI: getStingLen start invoke
2020-10-3 11:10:16.064 8925-8925/com.bj.gxz.jniapp D/JNI: getStingLen invoke done
2020-10-3 11:10:16.064 8925-8925/com.bj.gxz.jniapp D/JNI: ExceptionCheck 1
2020-10-3 11:10:16.065 8925-8925/com.bj.gxz.jniapp W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
2020-10-3 11:10:16.066 8925-8925/com.bj.gxz.jniapp W/System.err:     at com.bj.gxz.jniapp.exception.JNIException.getStingLen(JNIException.java:17)
2020-10-3 11:10:16.066 8925-8925/com.bj.gxz.jniapp W/System.err:     at com.bj.gxz.jniapp.exception.JNIException.nativeInvokeJavaException(Native Method)
2020-10-3 11:10:16.067 8925-8925/com.bj.gxz.jniapp W/System.err:     at com.bj.gxz.jniapp.MainActivity.onJniException(MainActivity.java:54)

程序不crash了。这样就可以在检测到异常后做一些异常处理(比如抛异常给java层处理,下文会讲)。当然平时写代码发现后还是需要按照正常思路解决的。

2.native方法如何抛出java类的异常

jni.h中提供了一个ThrowNew的方法来直接对java抛出异常

jint ThrowNew(JNIEnv *env, jclass clazz,
const char *message);Constructs an exception object from the specified class with the message specified by message and causes that exception to be thrown.

我们demo下

// 在java中声明一个抛出throws Exception的native方法
public native void nativeThrowException() throws Exception;
extern "C"
JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_exception_JNIException_nativeThrowException(JNIEnv *env, jobject thiz) {jclass newExcCls = env->FindClass("java/lang/RuntimeException");env->ThrowNew(newExcCls, "throw exception from c++ code");
}
// 调用public void onJniException(View view) {JNIException jniException = new JNIException();jniException.nativeInvokeJavaException();try {jniException.nativeThrowException();} catch (Exception e) {e.printStackTrace();Log.e(TAG, "nativeThrowException Exception:", e);}}
com.bj.gxz.jniapp E/JNI: nativeThrowException Exception:java.lang.RuntimeException: throw exception from c++ codeat com.bj.gxz.jniapp.exception.JNIException.nativeThrowException(Native Method)at com.bj.gxz.jniapp.MainActivity.onJniException(MainActivity.java:56)at java.lang.reflect.Method.invoke(Native Method)省略不重要的信息...

结果符合预期,c++代码里抛出的异常被我们捕获了。

这样看,这两个可以结合使用,当jni中代码出现异常的时候可以通过jni.h提供的方法ThrowNew 抛到java层,由java调用者执行自己的异常处理代码逻辑。是的。

源代码:https://github.com/ta893115871/JNIAPP

Android-JNI开发系列《三》-异常处理相关推荐

  1. Android JNI开发系列(二)HelloWorld

    2019独角兽企业重金招聘Python工程师标准>>> 入门HelloWorld 新建项目 Configure your new project部分选中 Include C++ Su ...

  2. Android 系统开发系列三

    今天写HAL硬件抽象层 1.添加HAL头文件 进入到 android-4.0.4_r1.2/hardware/libhardware/include/hardware 目录,创建 ttt.h 文件: ...

  3. Android 驱动开发系列三

    写blog的时候,发现跳章了,HAL硬件抽象层都没有写就到JNI了,这里补回来. 1.添加HAL头文件 进入到 android-4.0.4_r1.2/hardware/libhardware/incl ...

  4. android 字符串函数,Android JNI开发系列(六)字符串操作

    JNI字符串操作 字符串是引用数据类型,不属于基本数据类型 Java 使用unicode编码,C使用UTF-8,所以在操作中 C语言的字符串操作在头文件中 示例代码 public native Str ...

  5. Android蓝牙开发系列文章-蓝牙设备类型知多少?

    在写<Android蓝牙开发系列文章-蓝牙音箱连接>时,计划细化出一篇讲解蓝牙设备类型的文章,现在它来了~ 阅读其他内容,可以点击<Android蓝牙开发系列文章-策划篇>,或 ...

  6. Android商城开发系列

    Android商城开发系列(一)--开篇 Android商城开发系列(二)--App启动欢迎页面制作 Android商城开发系列(三)--使用Fragment+RadioButton实现商城底部导航栏 ...

  7. Android Camera开发系列(下)——自定义Camera实现拍照查看图片等功能

    Android Camera开发系列(下)--自定义Camera实现拍照查看图片等功能 Android Camera开发系列(上)--Camera的基本调用与实现拍照功能以及获取拍照图片加载大图片 上 ...

  8. Android蓝牙开发系列文章-蓝牙音箱连接

    经过一段时间的折腾,我的Android Studio终于可以正常工作了,期间遇到的坑记录在了文章<创建Android Studio 3.5第一个工程遇到的坑>. 我们在<Androi ...

  9. Android自定义控件开发系列(零)——基础原理篇

    在后边的文章中发现在说Android自定义时,有时候要重复解释很多东西,所以想想返回来增加一篇"基础原理篇",直接进入正题吧-- 首先的问题是:在Android项目开发中,什么时候 ...

  10. Android蓝牙开发系列文章-扫不到蓝牙设备,你的姿势对了吗?

    在写<Android蓝牙开发系列文章-蓝牙音箱连接>时,计划细化出两篇文章,分别是: 关于蓝牙设备类型分类的,这个已经完成了,阅读请点击<Android蓝牙开发系列文章-蓝牙设备类型 ...

最新文章

  1. 诗歌rails之如何写一个简单的Rails Plugin
  2. laravel 发送带附件的邮件
  3. java对话框背景图片插入_关于java编程窗体加背景图片的问题
  4. Socket编程(C语言实现)——TCP协议(网络间通信AF_INET)的流式(SOCK_STREAM)+报式(SOCK_DGRAM)传输【多线程+循环监听】
  5. OpenGL延迟着色之一
  6. 程序员必备软技能之科技趋势(一)
  7. 非空约束 mysql
  8. django-pure-pagination 分页插件
  9. MyBatis 缓存原来是这么一回事儿!| 原力计划
  10. 大数据分析优劣势有哪些
  11. 软件测试学习资料汇总
  12. Mac下安装Adobe pr
  13. 解决IDM下载城通网盘,一个网站不允许请求同一个文件两次,即使设置了快捷键也无用的问题
  14. 老毛桃u盘装系统linux,老毛桃U盘装系统教程详细步骤
  15. Star Way To Heaven
  16. 一个小程序走完诉讼全程,腾讯云加速推动“智慧法院”方案落地
  17. 2678v3支持内存频率_你听过E5-2678 v3这款CPU吗?我用它帮朋友干了件大事!
  18. 写论文中怎么插入参考文献
  19. POJ 1061 青蛙的约会(扩展欧几里德)
  20. 自定义LinearLayout实现淘宝详情页

热门文章

  1. 使用VS.NET2003操作SQLServer DTS.
  2. koa搭建node服务
  3. robot frame基础知识--变量
  4. 云计算99.9%可用性毫无意义 灾难恢复是关键
  5. 亚马逊表示并未放弃WP平台:正在打造新应用
  6. 误删除了Oracle的dbf文件后的解决方法
  7. mii-tool查看网卡状态
  8. 全文搜索引擎 Elasticsearch 安装
  9. Android 5.0 十大新特性
  10. Access链接表的使用