android jni release,Android JNI介绍(四)- 异常的处理

本系列文章列表:

在上一篇文章中,我们已经了解了如何进行Java和Native的交互,本文将介绍在JNI中如何进行Java异常处理。

一、主动抛出异常

在Java中我们能看到一些类中声明的native函数会抛出异常,比如:

java.io.FileOutputStream类中的:

private native void open0(String name, boolean append)

throws FileNotFoundException;

复制代码

android.hardware.Camera类中的:

public native final void setPreviewSurface(Surface surface) throws IOException;

public native final void setPreviewTexture(SurfaceTexture surfaceTexture) throws IOException;

复制代码

这些函数的实现都在native层,但是其声明却包含了可能会出现的异常,那就说明native其实是可以向Java抛异常的;

JNI也可以进行异常的捕获,我们也来看一下如何在native进行异常的处理。

二、使用JNI提供的函数处理异常

函数介绍

JNI为我们提供了一些函数进行异常的处理,如下:

// CATCH

// 如果有异常出现,返回值就是异常,如果没有异常,回传NULL

jthrowable ExceptionOccurred()

// 内部会调用异常对应的Java类的printStackTrace()函数

void ExceptionDescribe()

// 清除异常,清除异常后可以继续进行JNI操作

void ExceptionClear()

// 当前有没发生异常,若发生了,回传JNI_TRUE,否则回传JNI_FALSE

jboolean ExceptionCheck()

// THROW

// 向Java抛出一个异常

jint Throw(jthrowable obj)

// 选择异常的类型,填写异常的信息进行抛出

jint ThrowNew(jclass clazz, const char* message)复制代码

捕获异常

举个例子:

jmethodID toURLID = env->GetMethodID(fileClazz,"toURL","()Ljava/net/URL;");

jobject toURLValue = env->CallObjectMethod(fileObj, toURLID);

// this exception may occur:

// java.net.MalformedURLException

jthrowable error = env->ExceptionOccurred();

if (error != NULL) {

// 出现了异常

}else{

// 没出现异常

}

复制代码

抛出异常

需要注意的是,这里抛出的是Throwable,也就是说,不仅可以抛出Exception,还能抛出Error。

使用方法如下:

Java

public class CustomException extends Exception{

CustomException() {

super();

}

CustomException(String message) {

super(message);

}

}

复制代码public native String testExceptionNotCrash(int i) throws CustomException;

复制代码Native

extern "C" JNIEXPORT jstring

JNICALL

Java_com_wsy_jnidemo_MainActivity_testExceptionNotCrash(

JNIEnv *env,

jobject /* this */, jint i){

jstring hello = env->NewStringUTF("hello world");

if (i > 100) {

jclass exceptionCls = env->FindClass("com/wsy/jnidemo/CustomException");

env->ThrowNew(exceptionCls, "i must <= 100");

env->DeleteLocalRef(exceptionCls);

}

// 若出现异常,则env已不可使用一些方法,例如创建String将会crash,

// 文档说明:https://developer.android.google.cn/training/articles/perf-jni#exceptions_1

return hello;

}

复制代码

这里虽然没出现crash,但是Java层也是无法接收到hello这个String对象的

注意事项

需要注意的是,在exception发生时,在native层已不能调用大部分的方法,例如对象创建。

You must not call most JNI functions while an exception is pending. Your code is expected to notice the exception (via the function's return value,ExceptionCheck, or ExceptionOccurred) and return, or clear the exception and handle it.

The only JNI functions that you are allowed to call while an exception is pending are:

DeleteGlobalRef

DeleteLocalRef

DeleteWeakGlobalRef

ExceptionCheck

ExceptionClear

ExceptionDescribe

ExceptionOccurred

MonitorExit

PopLocalFrame

PushLocalFrame

ReleaseArrayElements

ReleasePrimitiveArrayCritical

ReleaseStringChars

ReleaseStringCritical

ReleaseStringUTFChars

例如在抛出exception后执行

env->NewStringUTF("hello world, after exception");

复制代码

将导致crash,就像这样。

e009c357f11875bc4e7e42b7cdc05ffa.png

但是我们刚才说到,当异常发生时,执行ExceptionDescribe会调用异常对应的Java类的printStackTrace()函数,这又是怎么做到的?明明已经出现了异常,为何还能进行和Java交互?

让我们看一看源码:

art/runtime/jni_internal.cc

static void ExceptionDescribe(JNIEnv* env){

ScopedObjectAccess soa(env);

// If we have no exception to describe, pass through.

if (!soa.Self()->GetException()) {

return;

}

StackHandleScope<1> hs(soa.Self());

Handle<:throwable> old_exception(

hs.NewHandle<:throwable>(soa.Self()->GetException()));

soa.Self()->ClearException();

ScopedLocalRef exception(env,

soa.AddLocalReference(old_exception.Get()));

ScopedLocalRef exception_class(env, env->GetObjectClass(exception.get()));

jmethodID mid = env->GetMethodID(exception_class.get(), "printStackTrace", "()V");

if (mid == nullptr) {

LOG(WARNING) << "JNI WARNING: no printStackTrace()V in "

<< mirror::Object::PrettyTypeOf(old_exception.Get());

} else {

env->CallVoidMethod(exception.get(), mid);

if (soa.Self()->IsExceptionPending()) {

LOG(WARNING) << "JNI WARNING: " << mirror::Object::PrettyTypeOf(soa.Self()->GetException())

<< " thrown while calling printStackTrace";

soa.Self()->ClearException();

}

}

soa.Self()->SetException(old_exception.Get());

}

复制代码

这里有个操作:

把Exception记下来

ExceptionClear

通过JNI调用异常的printStackTrace()函数(调用过程中出了异常还是会clear,稳)

把Exception放回去

三、crash的处理

哪怕JNI提供了这么些函数,但还是crash了咋办?

NDK中有一个很方便的debug工具:addr2line,可直接根据指令地址定位代码crash位置。

addr2line的位置在:{NDK安装目录}\toolchains\{平台相关目录}\prebuilt\windows-x86_64\bin目录。

我们先在native层写个错误示范代码,再运行,可以看到日志中出现了crash信息:c4ad67593929f1b3ae35dda27bd8d80b.png

找到工程相关的动态库文件,可以看到出现crash的so文件为/lib/x86/libnative-lib.so以及指令地址为00000E61,动态库文件在工程目录的位置如下图:

5a673d3e350cdb7b13478a4cca088adb.png

此时使用addr2line工具进行crash位置查找,用法为:

addr2line -e so文件路径 指令地址

复制代码

addr2line -e D:\android-project\study\JNIDemo\app\build\intermediates\cmake\debug\obj\x86\libnative-lib.so 00000e61

复制代码

运行结果如下8f3d0712cbc2ae5801ffb848fec26d29.png

可以看到定位到的导致crash的代码是native-lib.cpp文件的33行:05c5e27bcd6a4a35ed0b5e4e76731811.png

demo可直接clone:

详细的JNI使用方法:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值