JNI/NDK入门指南之JNI异常处理

          JNI/NDK入门指南之JNI异常处理


Android JNI/NDK入门指南目录

JNI/NDK入门指南之正确姿势了解JNI和NDK
JNI/NDK入门指南之JavaVM和JNIEnv
JNI/NDK入门指南之JNI数据类型,描述符详解
JNI/NDK入门指南之jobject和jclass
JNI/NDK入门指南之javah和javap的使用和集成
JNI/NDK入门指南之Eclipse集成NDK开发环境并使用
JNI/NDK入门指南之JNI动/静态注册全分析
JNI/NDK入门指南之JNI字符串处理
JNI/NDK入门指南之JNI访问数组
JNI/NDK入门指南之C/C++通过JNI访问Java实例属性和类静态属性
JNI/NDK入门指南之C/C++通过JNI访问Java实例方法和类静态方法
JNI/NDK入门指南之JNI异常处理
JNI/NDK入门指南之JNI多线程回调Java方法
JNI/NDK入门指南之正确姿势了解,使用,管理,缓存JNI引用
JNI/NDK入门指南之调用Java构造方法和父类实例方法
JNI/NDK入门指南之C/C++结构体和Java对象转换方式一
JNI/NDK入门指南之C/C++结构体和Java对象转换方式二



引言

  任何人都犯错误,连我们的好人强东哥都是如此,当然我们程序员也在这个行列之内了,哈哈(不是各位读者想象的那个错啊)。所以我们在开发或者调试JNI的过程中会因为种种情况无可避免的产生许多异常,异常不可怕可怕的是我们放任异常不做处理。那么今天我将要带领读者来深入探讨JNI中的异常处理,将我们的JNI程序打造的无懈可击。




一. JNI中异常处理策略

对于Java中的异常处理可以参见本篇你真的了解Java异常处理吗?中的介绍,总的来说就是Java的IDE工具将会提示检查型异常,而对于非检查型异常Java中可以用try…catch机制来捕获并处理异常。那么JNI中对于异常处理机制是怎么样的呢,由于JNI通常是和Java一起出现的我们这里通过和Java中的异常处理做对比来一探JNI中的异常处理。


1.1 Java和JNI处理异常区别

Java异常处理策略:

  • Java中可以用try…catch机制来捕获并处理异常;
  • 如果在Java中发生运行时异常,没有使用try…catch来捕获,会导致程序直接奔溃退出,后续的代码都不会被执行;
  • 编译时异常,是在方法声明时显示用throw声明了某一个异常,编译器要求在调用的时候必-须显示捕获处理;

JNI异常处理策略:

  • 而在JNI中,由于JNI没有像Java一样有try…catch…final这样的异常处理机制,面且在本地代码中调用某个JNI接口时如果发生了异常,后续的本地代码不会立即停止执行,而会继续往下执行后面的代码。

1.2 JNI异常处理具体套路

JNI函数在执行过程中会出现异常,其异常处理机制与Java和C++都不一样。下面让我们看看,我们在JNI的开发中,应该遵循怎么样的JNI异常处理套路。

JNI提供了两种检查异常的方法

  • 检查上一次 JNI函数调用的返回值是否为NULL,即大多数JNI 函数用显式方式表明当前线程是否有异常发生,下面我们通过代码判断GetFieldID 返回是否为NULL以检查是否发生异常。

Java端代码

package com.xxx.api.exception;

public class JNIExceptionManager {
    long mLong;
    int  mInt;
    short  mShort;
    static native void initIDs();
    static {
        System.loadLibrary("del_exception");
    }
}

JNI端代码

jfieldID FID_Exception_mLong;
jfieldID FID_Exception_mInt;
jfieldID FID_Exception_mShort;
JNIEXPORT void JNICALL Java_com_xxx_api_exception_JNIExceptionManager_initIDs
  (JNIEnv * env, jclass clazz)
{
	FID_Exception_mLong = env->GetFieldID(clazz, "mLong", "J");
	if (FID_Exception_mLong == NULL) { /* important check. */
		return; /* error occurred. */
	}
	FID_Exception_mInt =env->GetFieldID(clazz, "mInt", "I");
	if (FID_Exception_mInt == NULL) { /* important check. */
		return; /* error occurred. */
	}
	FID_Exception_mShort =env->GetFieldID(clazz, "mShort", "I");
	/* no checks necessary; we are about to return anyway */
}
  • 如果返回值不能表明是否发生异常,可以通过调用JNI函数ExceptionOccurred()或者ExceptionCheck来判断是否发生异常, 这个可以参考第三章节的介绍。

JNI中检查到异常后必须予以处理,这是因为JNI接口时如果发生了异常,后续的本地代码不会立即停止执行,而会继续往下执行后面的代码,所以必须处理。

处理异常的方法也有两种

  • Native方法可选择立即返回,这样异常就会在调用该Native方法的Java 代码中抛出。所以在Java代码中必须有捕获相应异常的代码,否则程序直接退出。
  • Native方法可以调用ExceptionClear()来清除异常,然后执行自己的异常处理代码。



二. JNI中异常处理函数

在前面的章节中我们介绍了JNI中异常处理的策略和套路,那么本章节我们将要探讨一下JNI为我们提供了那些JNI异常处理函数。这些函数就是我们的武器,我们只有把这些武器给捣鼓熟悉了,才能在实战中大放异彩。


2.1 Throw

函数原型: jint Throw(JNIEnv *env, jthrowable obj)
函数功能:抛出 java.lang.Throwable 异常对象,该异常对象可以通过ExceptionOccurred获取。
参数

  • env: JNIEnv 接口指针
  • obj java.lang.Throwable 对象

函数返回值: 成功时返回 0,失败时返回负数。
抛出异常: 抛出ava.lang.Throwable 对象 obj。


2.2 ThrowNew

函数原型: jint ThrowNew (JNIEnv *env , jclass clazz, const char *message)
函数功能:利用指定类的消息(由 message 相关消息指定)构造异常对象并抛出该异常。
参数

  • env: JNIEnv 接口指针
  • clazz: java.lang.Throwable 类或者及其子类
  • message: 用于构造java.lang.Throwable对象的具体消息

函数返回值: 成功时返回 0,失败时返回负数。
抛出异常: 新构造的ava.lang.Throwable 对象。


2.3 ExceptionOccurred

函数原型: jthrowable ExceptionOccurred (JNIEnv *env)
函数功能:确定前面JNI函数调用中否某个异常正被抛出。在平台相关代码调用 ExceptionClear()或Java 代码处理该异常前,异常将始终保持抛出状态。
参数

  • env: JNIEnv 接口指针

函数返回值: 返回正被抛出的异常对象,如果当前无异常被抛出,则返回NULL。


2.4 ExceptionCheck

函数原型: jboolean ExceptionCheck(JNIEnv* env)
函数功能:确定前面JNI函数调用中否某个异常正被抛出。在平台相关代码调用 ExceptionClear() 或 Java 代码处理该异常前,异常将始终保持抛出状态,功能和ExceptionOccurred类似只是返回值不同。
参数

  • env: JNIEnv 接口指针

函数返回值: 如果有异常则返回JNI_TRUE,否则返回JNI_FALSE。


2.5 ExceptionCheck

函数原型: jboolean ExceptionCheck(JNIEnv* env)
函数功能:确定前面JNI函数调用中否某个异常正被抛出。在平台相关代码调用 ExceptionClear() 或 Java 代码处理该异常前,异常将始终保持抛出状态,功能和ExceptionOccurred类似只是返回值不同。
参数

  • env: JNIEnv 接口指针

函数返回值: 如果有异常则返回JNI_TRUE,否则返回JNI_FALSE。


2.6 ExceptionDescribe

函数原型: void ExceptionDescribe(JNIEnv* env)
函数功能:将异常及堆栈的回溯输出到系统错误报告信道(通常是System.err)。
参数

  • env: JNIEnv 接口指针

函数返回值: 无。


2.7 ExceptionClear

函数原型: void ExceptionClear (JNIEnv* env)
函数功能:清除当前抛出的任何异常。如果当前无异常,则此例程不产生任何效果。
参数

  • env: JNIEnv 接口指针

函数返回值: 无。


2.8 FatalError

函数原型: void FatalError (JNIEnv *env, const char *msg)
函数功能:抛出致命错误并且不希望虚拟机进行修复。该函数无返回值。
参数

  • env: JNIEnv 接口指针
  • msg: 错误提示消息

函数返回值: 无。




三. JNI异常处理实战大操练

在前面的章节中我们对JNI异常处理的理论(不是伦理)知识进行了一轮非常详细的说教,光说不练那是摆把式。下面让我带领大伙实战一把。


3.1 JNI端捕获Java代码异常并抛出异常

这个实例中我们会在Java端方法中抛出一个异常,然后在JNI的本地方法中捕获并打印,然后在JNI本地方法中创新创建一个异常抛出。

Java端代码
Java本地方法类JNIExceptionManager.java代码:

package com.xxx.api.exception;

public class JNIExceptionManager {

    public native void throwException() throws IllegalArgumentException;//Native本地方法

    private void callBack() throws NullPointerException {//回调方法
        throw new NullPointerException("throw NullPointerException for test!");
    }
    static {
        System.loadLibrary("del_exception");
    }
}

Java测试代码

    private void operateJniException(){
        JNIExceptionManager mJniExceptionManager = new JNIExceptionManager();
        try{
            mJniExceptionManager.throwException();
        }catch(IllegalArgumentException e){//捕获异常
            e.printStackTrace();
        }       
    }

JNI端代码
Java中Native方法对应com_xxx_api_exception_JNIExceptionManager.h代码如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xxx_api_exception_JNIExceptionManager */

#ifndef _Included_com_xxx_api_exception_JNIExceptionManager
#define _Included_com_xxx_api_exception_JNIExceptionManager
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xxx_api_exception_JNIExceptionManager
 * Method:    throwException
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_api_exception_JNIExceptionManager_throwException
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

对应的com_xxx_api_exception_JNIExceptionManager.cpp代码如下:

#include "com_xxx_api_exception_JNIExceptionManager.h"
#include <stdio.h>
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#include <errno.h>

#define TAG "EXCEPTION"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)


/*
 * Class:     com_xxx_api_exception_JNIExceptionManager
 * Method:    throwException
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xxx_api_exception_JNIExceptionManager_throwException
  (JNIEnv * env, jobject object)
{
	jthrowable mThrowable;

	//1.通过GetObjectClass获取该Java类对象引用
	jclass clazz = env->GetObjectClass(object);

	//2.获取对象实例非静态方法callBack的方法ID
	jmethodID mid = env->GetMethodID(clazz, "callBack", "()V");
	if(NULL == mid)
	{
		LOGE(TAG,"GetMethodID callBack failed\n");
		return;
	}

	//3.调用Java实例对象方法callBack方法,此时会抛出异常需要对异常进行处理
	env->CallVoidMethod(object, mid);

	//4.捕获异常方式一,调用ExceptionOccurred判断是否有异常发生
	mThrowable = env->ExceptionOccurred();


	//5.捕获异常方式二,在没有将异常清除前,也可以使用ExceptionCheck检测是否有异常发生
	if(env->ExceptionCheck())
	{
		LOGE(TAG,"ExceptionCheck has exception\n");
	}
	
	if(mThrowable)
	{
		//6.捕获Java抛出的异常,并且打印异常,然后将其清理
		//然后在JNI的本地方法中创建一个异常返回给Java
		jclass newException;

		//7.打印异常信息堆栈
		env->ExceptionDescribe();
		//8.清除异常信息
		env->ExceptionClear();

		newException = env->FindClass("java/lang/IllegalArgumentException");
		if(NULL == newException)
		{
			//Unable to find the Class
			LOGE(TAG,"FindClass failed\n");
			return;
		}

		//9.抛出一个自定义异常信息
		env->ThrowNew(newException, "Sorry Java, Create exception for you!");
	}

	//10.删除局部引用
	env->DeleteLocalRef(mThrowable);
	env->DeleteLocalRef(clazz);
}

运行演示

W/System.err(11091): java.lang.NullPointerException: throw NullPointerException for test!
W/System.err(11091):    at com.xxx.api.exception.JNIExceptionManager.callBack(JNIExceptionManager.java:8)
W/System.err(11091):    at com.xxx.api.exception.JNIExceptionManager.throwException(Native Method)
W/System.err(11091):    at com.xxx.jni.MainActivity.operateJniException(MainActivity.java:136)
W/System.err(11091):    at com.xxx.jni.MainActivity.onCreate(MainActivity.java:33)
W/System.err(11091):    at android.app.Activity.performCreate(Activity.java:6033)
W/System.err(11091):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
W/System.err(11091):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2279)
W/System.err(11091):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2388)
W/System.err(11091):    at android.app.ActivityThread.access$800(ActivityThread.java:152)
W/System.err(11091):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1304)
W/System.err(11091):    at android.os.Handler.dispatchMessage(Handler.java:102)
W/System.err(11091):    at android.os.Looper.loop(Looper.java:135)
W/System.err(11091):    at android.app.ActivityThread.main(ActivityThread.java:5259)
W/System.err(11091):    at java.lang.reflect.Method.invoke(Native Method)
W/System.err(11091):    at java.lang.reflect.Method.invoke(Method.java:372)
W/System.err(11091):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)
W/System.err(11091):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697)
W/System.err(11091): java.lang.IllegalArgumentException: Sorry Java, Create exception for you!
W/System.err(11091):    at com.xxx.api.exception.JNIExceptionManager.throwException(Native Method)
W/System.err(11091):    at com.xxx.jni.MainActivity.operateJniException(MainActivity.java:136)
W/System.err(11091):    at com.xxx.jni.MainActivity.onCreate(MainActivity.java:33)
W/System.err(11091):    at android.app.Activity.performCreate(Activity.java:6033)
W/System.err(11091):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
W/System.err(11091):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2279)
W/System.err(11091):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2388)
W/System.err(11091):    at android.app.ActivityThread.access$800(ActivityThread.java:152)
W/System.err(11091):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1304)
W/System.err(11091):    at android.os.Handler.dispatchMessage(Handler.java:102)
W/System.err(11091):    at android.os.Looper.loop(Looper.java:135)
W/System.err(11091):    at android.app.ActivityThread.main(ActivityThread.java:5259)
W/System.err(11091):    at java.lang.reflect.Method.invoke(Native Method)
W/System.err(11091):    at java.lang.reflect.Method.invoke(Method.java:372)
W/System.err(11091):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)
W/System.err(11091):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697)

案例分析:
在本例中,Java类JNIExceptionManager中定义了一个throwException的Native方法并且显示声明了一个异常IllegalArgumentException,然后定义了一个普通方法callBack并且显示声明了一个异常NullPointerException。下面让我们对本地代码以一一分析。
(1) 通过GetObjectClass获取该Java类对象引用。
(2) 获取对象实例非静态方法callBack的方法ID。
(3) 通过JNI函数CallVoidMethod调用Java实例对象方法callBack方法,此时会抛出异常需要对异常进行处理,因为在Java中此方法会抛出异常。
(4) 捕获异常方式一,调用ExceptionOccurred判断是否有异常发生。
(5) 捕获异常方式二,在没有将异常清除前,也可以使用ExceptionCheck检测是否有异常发生。
(6) 捕获Java抛出的异常,并且打印异常,然后将其清理,然后在JNI的本地方法中创建一个异常返回给Java。
(7) 调用JNI函数ExceptionDescribe打印异常信息堆栈。
(8) 调用JNI函数ExceptionClear清除异常信息。
(9) 调用JNI函数ThrowNew抛出异常信息。
(10) 最后调用DeleteLocalRef删除前面创建的局部引用。

特别注意:
在JNI 中产生的异常(通过调用ThrowNew),与Java语言中异常发生的行为不同,JNI 中当前代码路径不会立即改变。在Java 中发生异常,VM自动把控制权转向try/catch中匹配的异常类型处理块。VM首先清空异常队列,然后执行异常处理块。相反,JNI中必须显式处理VM 的处理方式。


3.2 JNI异常处理工具代码大集合

针对JNI中的捕获异常和抛出异常,我们可以将其整理为工具代码,这样我们以后就可以通用了。

JNI中抛异常工具代码:

void
JNI_ThrowByName(JNIEnv *env, const char *name, const char *msg)
{
	//查找异常类
	jclass cls = env->FindClass(name);
	//判断是否找到该异常类
	if (cls != NULL) {
		env->ThrowNew(cls, msg);//抛出指定名称的异常
	}
	//释放局部变量
	env->DeleteLocalRef(cls);
}

JNI中检测工具代码:

int checkExecption(JNIEnv *env) {
    if(env->ExceptionCheck()) {//检测是否有异常
        env->ExceptionDescribe(); // 打印异常信息
        env->ExceptionClear();//清除异常信息
        return 1;
    }
    return -1;
}

测试代码:

if (checkExecption(env)) {//检测是否有异常
        LOGE(TAG, "jni exception happened at p1");
        JNU_ThrowByName(env, "java/lang/Exception", "exception from jni: jni exception happened at p1");//抛出异常
        return -1;
}



四. 总结

好了,通过前面章节的介绍我想读者对于JNI中异常的处理,应该已经有了一个清晰的认识了,老规矩在最后还是总结一把。
JNI异常处理流程如下

  • 对于绝大部分有返回值的JNI函数我们使用NULL检查是否有异常发生,如果返回值不能表明是否有异常发生,需要用 JNI提供的ExceptionOccurred或者ExceptionCheck检查但前线程是否有未处理异常。

  • 对于产生的异常,我们可以在本地代码中立即返回交由Java层处理,或者本地代码可以ExceptionClear 清空异常,然后自己做重新抛出等策略,即底层决定是否继续往下走还是重新生成异常抛给上层。




写在最后

  各位读者看官朋友们,关于JNI中异常处理就告一段落了。本篇将JNI异常处理涉及的流程,函数都一一讲解,然后加以实例讲解,只要仔细阅读本章,应该对JNI的异常处理没有什么畏惧的了。在最后麻烦读者朋友们如果本篇对你有帮助,关注和点赞一下,当然如果有错误和不足的地方也可以拍砖。青山不改绿水长流,各位江湖见!

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值