Exception handling in Java is very simple, we directly in the Java code try… The catch… Can. Suppose you use JNI technology to call a Java method in native code, and the Java method might throw an exception. How do you handle exceptions in JNI? We want to throw an exception in JNI again how do we do that? All of these issues are covered in JNI coding.

First, API review

1.1 Throw

jint Throw(JNIEnv *env, jthrowable obj);
Copy the code

Causes a java.lang.Throwable object to be thrown.

LINKAGE:

Index 13 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

Obj: a java.lang.Throwable object.

RETURNS:

Returns 0 on success; Negative on failure.

THROWS:

Java.lang.Throwable object obj.

1.2 ThrowNew

jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
Copy the code

Constructs an exception object from the specified class using the message specified by Message and causes the exception to be thrown.

LINKAGE:

Index 14 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

Clazz: a subclass of java.lang.Throwable.

Message: The message used to construct a java.lang.Throwable object. The string is encoded in modified UTF-8.

RETURNS:

Returns 0 on success; Negative on failure.

THROWS:

Newly constructed java.lang.Throwable object.

1.3 ExceptionOccurred

jthrowable ExceptionOccurred(JNIEnv *env);
Copy the code

Determines whether an exception is thrown. Exceptions are thrown until the native code calls ExceptionClear() or Java code handles the exception.

LINKAGE:

Index 15 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

RETURNS:

Returns the exception object currently being thrown, or NULL if no exception is currently being thrown.

1.4 ExceptionDescribe

void ExceptionDescribe(JNIEnv *env);
Copy the code

Print stack exceptions and tracebacks to a system error reporting channel, such as STderr. This is a convenience routine for debugging.

LINKAGE:

Index 16 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

1.5 ExceptionClear

void ExceptionClear(JNIEnv *env);
Copy the code

Clears any exceptions that are currently being thrown. This routine is invalid if no exception is currently thrown.

LINKAGE:

Index 17 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

1.6 a FatalError

void FatalError(JNIEnv *env, const char *msg);
Copy the code

Raises a fatal error and does not want the VM to recover. This function does not return.

LINKAGE:

Index 18 in the JNIEnv interface function table.

PARAMETERS:

Env: JNI interface pointer.

MSG: error message. The string is encoded in modified UTF-8.

1.7 ExceptionCheck

We introduced a convenience function to check for pending exceptions without creating a local reference to the exception object.

jboolean ExceptionCheck(JNIEnv *env);
Copy the code

Return JNI_TRUE if there is a pending exception; Otherwise, JNI_FALSE is returned.

LINKAGE:

Index 228 in the JNIEnv interface function table.

SINCE:

The JDK/JRE 1.2

Second, the use of

2.1 Routine code

The divZero() method in Java code throws a ArithmeticException since the divisor is zero. The functions mentioned in the API are used in JNI to handle and throw exceptions, respectively. Practice the various JNI functions.

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 exception handling...") ); nativeDiv(); //nativeThrowException(); //nativeFatalError(); } private void divZero() { int i = 5 / 0; } public native void nativeDiv(); public native void nativeThrowException(); public native void nativeFatalError(); }Copy the code

Native code:

#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); // Determine whether an exception is thrown. ExceptionClear() jthrowable jthrow = env->ExceptionOccurred(); ExceptionCheck() checks for exceptions. If exceptions occur, return JNI_TRUE. Otherwise return JNI_FALSE if (jthrow && env->ExceptionCheck()) {env->ExceptionDescribe(); // Prints stack exceptions and tracebacks to the system error reporting channel, such as stderr. This is a convenience routine for debugging. env->ExceptionClear(); // Clears any exceptions that are currently being thrown. This routine is invalid if no exception is currently thrown. LOGI("Run Java method find exception."); } jclass arithmeticExceptionCls = env->FindClass("java/lang/ArithmeticException"); if (NULL ! = arithmeticExceptionCls) {construct an exception object from the specified class using the specified 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); Env ->Throw(THR); } JNIEXPORT void JNICALL Java_ndk_example_com_ndkexample_MainActivity_nativeFatalError(JNIEnv *env, jobject) {// Raise fatal error, And you don't want the VM to recover. Env ->FatalError("FatalError MSG."); }}Copy the code

2.2 JNI catches exceptions

The two functions ExceptionOccurred and ExceptionCheck are used in Native code to detect whether exceptions are thrown. The difference between the two functions is that the former returns the exception object currently being thrown, or NULL if no exception is currently being thrown. The latter checks to see if an exception is thrown and returns JNI_TRUE if there is a pending exception, and JNI_FALSE otherwise. The ExceptionClear function clears any exceptions that are currently being thrown. The ExceptionDescribe function prints stack exceptions and tracebacks to the system error reporting channel for easy debugging.

Results of running the code in 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 the 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)Copy the code

It can be seen from the result that the error log was hit twice. In fact, the first time was the function of ExceptionDescribe function, and the second time was the log that actually caused the exception to be thrown. And Run Java Method find Exception. And Run After JNI Throw New Exception. Both points are printed, indicating that Native threw an exception and did not immediately stop executing at the throw point, which is different from Java code.

2.3 JNI throws an exception

Native code uses throws and ThrowNew to Throw exceptions. The difference is that the Throw method takes jthrowable as an argument, whereas ThrowNew takes a java.lang.Throwable subclass and the message used to construct the java.lang.Throwable object as arguments. FatalError raises a FatalError and does not expect the VM to recover. This function does not return.

Uncomment the nativeThrowException() method in the routine and run the result again as follows:

10-26 10:11:06. 618 11967-11967 /ndk.example.com.ndkexample D/AndroidRuntime: Shutting down the 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)Copy the code

The above results see Exception from native. This message is thrown by the Throw method.

Finally, open the nativeFatalError() comment and look at the result:

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]Copy the code

The consequences look worse than the last few! After all, FatalError raises fatal errors and does not want the VM to recover, so use it with caution.

Third, summary

Calling Java methods in JNI that may throw exceptions is necessary for exception handling. It’s not hard to see how JNI handles exceptions from the previous example:

1.ExceptionCheck or ExceptionOccurred to check whether exceptions are thrown;

2.ExceptionDescribe prints exception stack for easy debugging;

3.ExceptionClear, which clears any exceptions thrown.

If you need to Throw an exception in JNI, you need to use the Throw or ThrowNew methods. FatalError can be used if a FatalError is raised in JNI and the VM is not expected to recover.