在上一篇文章中,咱们已经了解了如何进行Java和Native的交互,本文将介绍在JNI中如何进行Java异常处理。java
在Java中咱们能看到一些类中声明的native函数会抛出异常,好比:android
java.io.FileOutputStream
类中的:git
private native void open0(String name, boolean append)
throws FileNotFoundException;
复制代码
android.hardware.Camera
类中的:github
public native final void setPreviewSurface(Surface surface) throws IOException;
public native final void setPreviewTexture(SurfaceTexture surfaceTexture) throws IOException;
复制代码
这些函数的实现都在native层,可是其声明却包含了可能会出现的异常,那就说明native实际上是能够向Java抛异常的;
JNI也能够进行异常的捕获,咱们也来看一下如何在native进行异常的处理。windows
函数介绍bash
JNI为咱们提供了一些函数进行异常的处理,以下:app
// 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)
复制代码
捕获异常ide
举个例子:函数
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
。 使用方法以下:
public class CustomException extends Exception {
CustomException() {
super();
}
CustomException(String message) {
super(message);
}
}
复制代码
public native String testExceptionNotCrash(int i) throws CustomException;
复制代码
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层已不能调用大部分的方法,例如对象建立。
NDK官网说明以下:
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,就像这样。
可是咱们刚才说到,当异常发生时,执行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<mirror::Throwable> old_exception(
hs.NewHandle<mirror::Throwable>(soa.Self()->GetException()));
soa.Self()->ClearException();
ScopedLocalRef<jthrowable> exception(env,
soa.AddLocalReference<jthrowable>(old_exception.Get()));
ScopedLocalRef<jclass> 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());
}
复制代码
这里有个操做:
printStackTrace()
函数(调用过程当中出了异常仍是会clear,稳)哪怕JNI提供了这么些函数,但仍是crash了咋办?
NDK中有一个很方便的debug工具:addr2line
,可直接根据指令地址定位代码crash位置。 addr2line
的位置在:{NDK安装目录}\toolchains\{平台相关目录}\prebuilt\windows-x86_64\bin
目录。 咱们先在native层写个错误示范代码,再运行,能够看到日志中出现了crash信息:
/lib/x86/libnative-lib.so
以及指令地址为
00000E61
,动态库文件在工程目录的位置以下图:
addr2line
工具进行crash位置查找,用法为:
addr2line -e so文件路径 指令地址
复制代码
如
addr2line -e D:\android-project\study\JNIDemo\app\build\intermediates\cmake\debug\obj\x86\libnative-lib.so 00000e61
复制代码
运行结果以下
能够看到定位到的致使crash的代码是native-lib.cpp
文件的33行: