Java中异常处理非常简单,我们直接在Java代码中try…catch…即可。假设使用JNI技术在native代码中调用Java方法,而这个Java方法有可能抛出异常,如何在JNI中进行异常处理呢?我们又想在JNI中抛出异常具体怎样操作?这些问题都会在JNI编码中进行涉及。
一、API回顾
1.1 Throw
jint Throw(JNIEnv *env, jthrowable obj);
导致抛出java.lang.Throwable对象。
LINKAGE:
JNIEnv接口函数表中的索引13。
PARAMETERS:
env:JNI接口指针。
obj:一个java.lang.Throwable对象。
RETURNS:
成功时返回0; 失败时为负值。
THROWS:
java.lang.Throwable对象obj。
1.2 ThrowNew
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
使用message指定的消息从指定的类构造一个异常对象,并导致抛出该异常。
LINKAGE:
JNIEnv接口函数表中的索引14。
PARAMETERS:
env:JNI接口指针。
clazz:java.lang.Throwable的子类。
message:用于构造java.lang.Throwable对象的消息。该字符串以修改后的UTF-8编码。
RETURNS:
成功时返回0; 失败时为负值。
THROWS:
新构造的java.lang.Throwable对象。
1.3 ExceptionOccurred
jthrowable ExceptionOccurred(JNIEnv *env);
确定是否抛出异常。在本地代码调用ExceptionClear()或Java代码处理异常之前,异常会一直抛出。
LINKAGE:
JNIEnv接口函数表中的索引15。
PARAMETERS:
env:JNI接口指针。
RETURNS:
返回当前正在抛出的异常对象,如果当前没有抛出异常,则返回NULL。
1.4 ExceptionDescribe
void ExceptionDescribe(JNIEnv *env);
将堆栈的异常和回溯打印到系统错误报告通道,例如stderr。这是为调试提供的便利例程。
LINKAGE:
JNIEnv接口函数表中的索引16。
PARAMETERS:
env:JNI接口指针。
1.5 ExceptionClear
void ExceptionClear(JNIEnv *env);
清除当前正在抛出的任何异常。 如果当前没有抛出异常,则此例程无效。
LINKAGE:
JNIEnv接口函数表中的索引17。
PARAMETERS:
env:JNI接口指针。
1.6 FatalError
void FatalError(JNIEnv *env, const char *msg);
引发致命错误,并且不希望VM恢复。此功能不返回。
LINKAGE:
JNIEnv接口函数表中的索引18。
PARAMETERS:
env:JNI接口指针。
msg:错误消息。该字符串以修改后的UTF-8编码。
1.7 ExceptionCheck
我们引入了一个便捷函数来检查挂起的异常,而不创建对异常对象的局部引用。
jboolean ExceptionCheck(JNIEnv *env);
存在挂起异常时返回JNI_TRUE; 否则,返回JNI_FALSE。
LINKAGE:
JNIEnv接口函数表中的索引228。
SINCE:
JDK/JRE 1.2
二、使用
2.1 例程代码
Java代码中divZero()方法,由于除数为零会抛出ArithmeticException异常。在JNI中分别使用了API中提及的函数对异常进行处理和抛出。对各个JNI函数进行实战。
package ndk.example.com.ndkexample;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Main";
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText("JNI异常处理...");
nativeDiv();
//nativeThrowException();
//nativeFatalError();
}
private void divZero() {
int i = 5 / 0;
}
public native void nativeDiv();
public native void nativeThrowException();
public native void nativeFatalError();
}
Native代码:
#include <jni.h>
#include <stdio.h>
#include <android/log.h>
#define TAG "Native"
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
extern "C" {
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeDiv(JNIEnv *env, jobject instance) {
jclass clz = env->GetObjectClass(instance);
if (NULL == clz) {
return;
}
jmethodID divMid = env->GetMethodID(clz, "divZero", "()V");
if (NULL == divMid) {
return;
}
env->CallVoidMethod(instance, divMid);
// 确定是否抛出异常。在本地代码调用ExceptionClear()或Java代码处理异常之前,异常会一直抛出
jthrowable jthrow = env->ExceptionOccurred();
// ExceptionCheck()检查是否发生了异常,若有异常返回JNI_TRUE,否则返回JNI_FALSE
if (jthrow && env->ExceptionCheck()) {
env->ExceptionDescribe();//将堆栈的异常和回溯打印到系统错误报告通道,例如stderr。这是为调试提供的便利例程。
env->ExceptionClear();//清除当前正在抛出的任何异常。如果当前没有抛出异常,则此例程无效。
LOGI("Run Java method find exception.");
}
jclass arithmeticExceptionCls = env->FindClass("java/lang/ArithmeticException");
if (NULL != arithmeticExceptionCls) {
// 使用message指定的消息从指定的类构造一个异常对象,并导致抛出该异常
env->ThrowNew(arithmeticExceptionCls, "Throw New Exception: divide by zero");
}
LOGI("Run After JNI Throw New Exception.");
}
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeThrowException(JNIEnv *env, jobject) {
jclass arithmeticExceptionCls = env->FindClass("java/lang/ArithmeticException");
if (NULL == arithmeticExceptionCls) {
return;
}
jmethodID initMethodID = (env)->GetMethodID(arithmeticExceptionCls, "<init>", "(Ljava/lang/String;)V");
if (NULL == initMethodID) {
return;
}
char infoBuf[256] = {0};
sprintf(infoBuf, "Exception from native.");
jstring info = (env)->NewStringUTF(infoBuf);
jthrowable thr = (jthrowable) (env)->NewObject(arithmeticExceptionCls, initMethodID, info);
//导致抛出java.lang.Throwable对象
env->Throw(thr);
}
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeFatalError(JNIEnv *env, jobject) {
//引发致命错误,并且不希望VM恢复。此功能不返回
env->FatalError("FatalError msg.");
}
}
2.2 JNI捕获异常
Native代码中使用ExceptionOccurred和ExceptionCheck这两个函数,可检测是否抛出异常。这两个函数的区别在于前者返回当前正在抛出的异常对象,如果当前没有抛出异常,则返回NULL。后者用来检查是否抛出异常,存在挂起异常时返回JNI_TRUE,否则返回JNI_FALSE。ExceptionClear函数清除当前正在抛出的任何异常。ExceptionDescribe函数将堆栈的异常和回溯打印到系统错误报告通道,方便调试。
运行2.1中的代码结果:
10-26 08:39:58.994 11303-11303/ndk.example.com.ndkexample W/System.err: java.lang.ArithmeticException: divide by zero
at ndk.example.com.ndkexample.MainActivity.divZero(MainActivity.java:28)
at ndk.example.com.ndkexample.MainActivity.nativeDiv(Native Method)
at ndk.example.com.ndkexample.MainActivity.onCreate(MainActivity.java:22)
at android.app.Activity.performCreate(Activity.java:6010)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1129)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2413)
at android.app.ActivityThread.access$800(ActivityThread.java:155)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1317)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5343)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:702)
10-26 08:39:58.995 11303-11303/ndk.example.com.ndkexample I/Native: Run Java method find exception.
Run After JNI Throw New Exception.
10-26 08:39:58.995 11303-11303/ndk.example.com.ndkexample D/AndroidRuntime: Shutting down VM
--------- beginning of crash
10-26 08:39:58.996 11303-11303/ndk.example.com.ndkexample E/AndroidRuntime: FATAL EXCEPTION: main
Process: ndk.example.com.ndkexample, PID: 11303
java.lang.RuntimeException: Unable to start activity ComponentInfo{ndk.example.com.ndkexample/ndk.example.com.ndkexample.MainActivity}: java.lang.ArithmeticException: Throw New Exception: divide by zero
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2339)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2413)
at android.app.ActivityThread.access$800(ActivityThread.java:155)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1317)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5343)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:702)
Caused by: java.lang.ArithmeticException: Throw New Exception: divide by zero
at ndk.example.com.ndkexample.MainActivity.nativeDiv(Native Method)
at ndk.example.com.ndkexample.MainActivity.onCreate(MainActivity.java:22)
at android.app.Activity.performCreate(Activity.java:6010)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1129)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2413)
at android.app.ActivityThread.access$800(ActivityThread.java:155)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1317)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5343)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:702)
从结果可以看出,出错log打了两次,实际上第一次是ExceptionDescribe函数的作用,第二次才是真正导致异常抛出的log。而且Run Java method find exception.和Run After JNI Throw New Exception.这两个点都打印了出来,这说明了Native中抛出(ThrowNew)了异常,并没有马上停止在抛出点,还在继续执行,这和Java代码还是有区别的。
2.3 JNI抛出异常
Native代码中使用Throw和ThrowNew可抛出异常。区别在于Throw方法需要传入jthrowable作为参数,而ThrowNew需要传入java.lang.Throwable的子类和用于构造java.lang.Throwable对象的消息作为参数。FatalError则引发致命错误,并且不希望VM恢复,此功能不返回。
将例程中的nativeThrowException()方法注释去掉,再次运行结果如下:
10-26 10:11:06.618 11967-11967/ndk.example.com.ndkexample D/AndroidRuntime: Shutting down VM
--------- beginning of crash
10-26 10:11:06.619 11967-11967/ndk.example.com.ndkexample E/AndroidRuntime: FATAL EXCEPTION: main
Process: ndk.example.com.ndkexample, PID: 11967
java.lang.RuntimeException: Unable to start activity ComponentInfo{ndk.example.com.ndkexample/ndk.example.com.ndkexample.MainActivity}: java.lang.ArithmeticException: Exception from native.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2339)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2413)
at android.app.ActivityThread.access$800(ActivityThread.java:155)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1317)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5343)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:702)
Caused by: java.lang.ArithmeticException: Exception from native.
at ndk.example.com.ndkexample.MainActivity.nativeThrowException(Native Method)
at ndk.example.com.ndkexample.MainActivity.onCreate(MainActivity.java:23)
at android.app.Activity.performCreate(Activity.java:6010)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1129)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2413)
at android.app.ActivityThread.access$800(ActivityThread.java:155)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1317)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5343)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:702)
上面的运行结果看到了Exception from native.这条信息,说明正是通过Throw方法抛出的。
最后打开nativeFatalError()注释再看一下运行结果:
10-26 10:16:11.770 12954-12954/ndk.example.com.ndkexample A/art: art/runtime/jni_internal.cc:769] JNI FatalError called: FatalError msg.
10-26 10:16:11.925 12954-12954/ndk.example.com.ndkexample A/art: art/runtime/runtime.cc:314] Runtime aborting...
art/runtime/runtime.cc:314] Aborting thread:
art/runtime/runtime.cc:314] "main" prio=5 tid=1 Runnable
art/runtime/runtime.cc:314] | group="" sCount=0 dsCount=0 obj=0x73cddfa8 self=0x7f7e495000
art/runtime/runtime.cc:314] | sysTid=12954 nice=0 cgrp=apps sched=0/0 handle=0x7f8212fea0
art/runtime/runtime.cc:314] | state=R schedstat=( 90633388 7482185 83 ) utm=7 stm=2 core=5 HZ=100
art/runtime/runtime.cc:314] | stack=0x7fc0145000-0x7fc0147000 stackSize=8MB
art/runtime/runtime.cc:314] | held mutexes= "abort lock" "mutator lock"(shared held)
art/runtime/runtime.cc:314] native: #00 pc 000040f4 /system/lib64/libbacktrace_libc++.so (_ZN9Backtrace6UnwindEmP8ucontext+28)
art/runtime/runtime.cc:314] native: #01 pc 0000001c ???
art/runtime/runtime.cc:314] at ndk.example.com.ndkexample.MainActivity.nativeFatalError(Native method)
art/runtime/runtime.cc:314] at ndk.example.com.ndkexample.MainActivity.onCreate(MainActivity.java:24)
art/runtime/runtime.cc:314] at android.app.Activity.performCreate(Activity.java:6010)
art/runtime/runtime.cc:314] at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1129)
art/runtime/runtime.cc:314] at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292)
art/runtime/runtime.cc:314] at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2413)
art/runtime/runtime.cc:314] at android.app.ActivityThread.access$800(ActivityThread.java:155)
art/runtime/runtime.cc:314] at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1317)
art/runtime/runtime.cc:314] at android.os.Handler.dispatchMessage(Handler.java:102)
art/runtime/runtime.cc:314] at android.os.Looper.loop(Looper.java:135)
art/runtime/runtime.cc:314] at android.app.ActivityThread.main(ActivityThread.java:5343)
art/runtime/runtime.cc:314] at java.lang.reflect.Method.invoke!(Native method)
art/runtime/runtime.cc:314] at java.lang.reflect.Method.invoke(Method.java:372)
art/runtime/runtime.cc:314] at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
art/runtime/runtime.cc:314] at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:702)
art/runtime/runtime.cc:314] Dumping all threads without appropriate locks held: thread list lock
art/runtime/runtime.cc:314] All threads:
......
art/runtime/runtime.cc:314] "Binder_2" prio=5 tid=15 Native
art/runtime/runtime.cc:314] | group="" sCount=0 dsCount=0 obj=0x12c830a0 self=0x7f7e4a4000
art/runtime/runtime.cc:314] | sysTid=12973 nice=0 cgrp=apps sched=0/0 handle=0x7f77025300
art/runtime/runtime.cc:314] | state=S schedstat=( 718855 171043 3 ) utm=0 stm=0 core=2 HZ=100
art/runtime/runtime.cc:314] | stack=0x7f77b20000-0x7f77b22000 stackSize=1012KB
art/runtime/runtime.cc:314] | held mutexes=
art/runtime/runtime.cc:314] kernel: __switch_to+0x70/0x7c
art/runtime/runtime.cc:314] kernel: binder_thread_read+0xd8c/0xecc
art/runtime/runtime.cc:314] kernel: binder_ioctl+0x3f8/0x824
art/runtime/runtime.cc:314] kernel: do_vfs_ioctl+0x4a4/0x578
art/runtime/runtime.cc:314] kernel: SyS_ioctl+0x5c/0x88
art/runtime/runtime.cc:314] kernel: cpu_switch_to+0x48/0x4c
art/runtime/runtime.cc:314] native: #00 pc 0006227c /system/lib64/libc.so (__ioctl+4)
art/runtime/runtime.cc:314] native: #01 pc 0008a0dc /system/lib64/libc.so (ioctl+100)
art/runtime/runtime.cc:314] native: #02 pc 0002de1c /system/lib64/libbinder.so (_ZN7android14IPCThreadState14talkWithDriverEb+164)
art/runtime/runtime.cc:314] native: #03 pc 0002e690 /system/lib64/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+24)
art/runtime/runtime.cc:314] native: #04 pc 0002e748 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+76)
art/runtime/runtime.cc:314] native: #05 pc 000360ec /system/lib64/libbinder.so (???)
art/runtime/runtime.cc:314] native: #06 pc 00016a68 /system/lib64/libutils.so (_ZN7android6Thread11_threadLoopEPv+208)
art/runtime/runtime.cc:314] native: #07 pc 00090454 /system/lib64/libandroid_runtime.so (_ZN7android14AndroidRuntime15javaThreadShellEPv+96)
art/runtime/runtime.cc:314] native: #08 pc 0001647c /system/lib64/libutils.so (???)
art/runtime/runtime.cc:314] native: #09 pc 0001f5c4 /system/lib64/libc.so (_ZL15__pthread_startPv+52)
art/runtime/runtime.cc:314] native: #10 pc 0001b7b0 /system/lib64/libc.so (__start_thread+16)
art/runtime/runtime.cc:314] (no managed stack frames)
art/runtime/runtime.cc:314]
art/runtime/runtime.cc:314]
后果看起来了比前几次都要严重!毕竟FatalError引发致命错误,并且不希望VM恢复,使用要谨慎。
三、总结
在JNI中调用可能抛出异常的Java方法,对异常处理是必要的。从前面的例程不难得出JNI处理异常的流程:
1.ExceptionCheck或ExceptionOccurred,检查是否抛出了异常;
2.ExceptionDescribe打印异常堆栈,方便调试;
3.ExceptionClear,清除抛出的任何异常。
如果需要在JNI中抛出异常,则需要使用Throw或ThrowNew方法。如果在JNI中引发致命错误并且不希望VM恢复,则可以使用FatalError。