【JNI编程】JNI异常

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。

发布了64 篇原创文章 · 获赞 42 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/tyyj90/article/details/87876810
JNI