JNI functions can fail because of system constraint (for example, lack of memory) or invalid arguments (for example, passing a native UTF-8 string when the function is expecting a UTF-16 string). This recipe discusses how to handle errors and exceptions in JNI programming.
The following recipes should be read first before proceeding with this recipe:
Follow these steps to create a sample Android project that illustrates errors and exception handling in JNI:
ExceptionHandling
. Set the package name as cookbook.chapter2
. Create an activity named ExceptionHandlingActivity
. Under the project, create a folder named jni
. Refer to the Loading native libraries and registering native methods recipe of this chapter for more detailed instructions.exceptiontest.c
under the jn
i folder, then implement the ExceptionDemo
and FatalErrorDemo
methods.ExceptionHandlingActivity.java
file by adding code to load the native library, then declare and invoke the native methods.Android.mk
build file, and build the native library. Refer to steps 8 to 10 of the the Loading native libraries and registering native methods recipe of this chapter for more details.This recipe discusses error checking and exception handling at JNI:
FindClass
function returns NULL
to indicate it failed to load the class. Many other functions do not use the return value to signal failure; instead an exception is thrown.For the first group of functions, we can simply check the return value to see if an error occurs. For the second group of functions, JNI defines two functions to check for exceptions, as follows:
jboolean ExceptionCheck(JNIEnv *env); jthrowable ExceptionOccurred(JNIEnv *env);
The first function returns JNI_TRUE
to indicate that an exception occurs, and JNI_FALSE
otherwise. The second function returns a local reference to the exception. When the second function is used, an additional JNI function can be called to examine the details of the exception:
void ExceptionDescribe(JNIEnv *env);
The function prints the exception and a back trace of the stack to the logcat.
In the native method
ExceptionDemo
, we used both approaches to check for occurrence of exceptions and
ExceptionDescribe
to print out the exception details.
There are generally two ways to handle an exception. The first approach is to free the resources allocated at JNI and return. This will leave the responsibility of handling the exception to the caller of the native method.
The second practice is to clear the exception and continue executing. This is done through the following JNI function call:
void ExceptionClear(JNIEnv *env);
In the native method ExceptionDemo
, we used the second approach to clear java.lang.NullPointerException
, and the first approach to return java.lang.RuntimeException
to the caller, which is the Java method callExceptionDemo
at ExceptionHandlingActivity.java
.
When an exception is pending, not all the JNI functions can be called safely. The following functions are allowed when there are pending exceptions:
DeleteGlobalRef
DeleteLocalRef
DeleteWeakGlobalRef
ExceptionCheck
ExceptionClear
ExceptionDescribe
ExceptionOccurred
MonitorExit
PopLocalFrame
PushLocalFrame
Release<PrimitiveType>ArrayElements
ReleasePrimitiveArrayCritical
ReleaseStringChars
ReleaseStringCritical
ReleaseStringUTFChars
They're basically exception check and handle functions, or functions that clear resources allocated at native code.
jint Throw(JNIEnv *env, jthrowable obj); jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
The first function accepts a reference to a
jthrowable
object and throws the exception, while the second function accepts a reference to an exception class. It will create an exception object
of the clazz
class with the message argument and throw it.
In the ExceptionDemo
native method, we used the
ThrowNew
function to throw java.lang.NullPointerException
and a Throw
function to throw java.lang.RuntimeException
.
The following logcat output indicates how the exceptions are checked, cleared, and thrown:
The last exception is not cleared at the native method. In the Java code, we catch the exception and display the message on the phone screen:
FatalError
, as follows, to raise a fatal error:void FatalError(JNIEnv *env, const char *msg);
This function accepts a message and prints it to logcat. After that, the VM instance for the application is terminated. We demonstrated the usage of this function in the native method FatalErrorDemo
and Java method callFatalErrorDemo
. The following output is captured at logcat:
Note that the code after the FatalError
function is never executed, in neither the native nor Java code, because
FatalError
never returns, and the VM instance is terminated. On my Android device, this does not lead the Android application to crash, but
causes the application to freeze instead.
C++ exception is currently not supported on Android JNI programming. In other words, the native C++ exceptions do not propagate to Java world through JNI. Therefore, we should handle C++ exceptions within C++ code. Alternatively, we can write a C wrapper to throw an exception or return an error code to Java.