Постановка задачи
Если в вашем приложении есть части написанные при помощи JNI, то вам наверняка захочется отслеживать ошибки не просто возвращая их код на выходе из метода, как это делали наши дедушки, а «выкидывать» полноценные Java-исключения из нативного кода, а также отслеживать состояние системы на предмет возникновения исключительной ситуации.
Как это делается. Живой пример
О том как использовать в Java-коде нативные функции и JNI-вызовы я уже рассказывал в этой статье, так что сразу к делу.
Создадим одну Activity с двумя кнопками, по нажатию на первую будет возбуждать Exception, по нажатию на вторую — RuntimeException. Объявим эти метды прямо в Activity следующим образом:
public native void nativeThrowRuntumeException();
public native void nativeThrowException() throws Exception;
static {
System.loadLibrary("native");
}

За простым интерфейсом с парой кнопок скрывается могучий C++
Тут же мы выполнили загрузку нативной библиотеки при помощи System.loadLibrary()
. Как видите, throws
мы объявляем как для обычного метода. Для RuntimeException
и его наследников объявлять throws
не нужно.
Теперь при помощи javah сгенирируем заголовочный файл следующего содержания:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class by_idev_jniexceptions_MainActivity */
#ifndef _Included_by_idev_jniexceptions_MainActivity
#define _Included_by_idev_jniexceptions_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: by_idev_jniexceptions_MainActivity
* Method: nativeThrowRuntumeException
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_by_idev_jniexceptions_MainActivity_nativeThrowRuntumeException
(JNIEnv *, jobject);
/*
* Class: by_idev_jniexceptions_MainActivity
* Method: nativeThrowException
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_by_idev_jniexceptions_MainActivity_nativeThrowException
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Логика будет реализована в Native.cpp:
#include "by_idev_jniexceptions_MainActivity.h"
#include <stdio.h>
#include <android/log.h>
//логирование при помоди стандартного лога Android
#define LOG_D(x) __android_log_write(ANDROID_LOG_DEBUG, "Native code", x)
//Общий метод возбуждающий исключение с сообщением по имени класса-исключения
void throwJavaException(JNIEnv* pEnv, const char* pClazzName, const char* pMessage) {
jclass clazz = pEnv->FindClass(pClazzName);
if (clazz) {
pEnv->ThrowNew(clazz, pMessage);
}
pEnv->DeleteLocalRef(clazz);
}
//В этом методе мы используем интересную особенность JNIEnv - возможность узнать, были ли исключения
void logExceptionStatus(JNIEnv* pEnv) {
//Простейший способ отследить наличие исключения для текущего JNI-вызова
jboolean isException = pEnv->ExceptionCheck();
const char* logMessageTemplate = "Exception occured: %s";
char logMessage[64];
sprintf(logMessage, logMessageTemplate, (isException ? "true" : "false"));
LOG_D(logMessage);
}
JNIEXPORT void JNICALL Java_by_idev_jniexceptions_MainActivity_nativeThrowRuntumeException(JNIEnv *pEnv, jobject pThis) {
logExceptionStatus(pEnv);
const char* clazzName = "java/lang/RuntimeException";
const char* message = "Runtime exception from C++ code!";
throwJavaException(pEnv, clazzName, message);
logExceptionStatus(pEnv);
}
JNIEXPORT void JNICALL Java_by_idev_jniexceptions_MainActivity_nativeThrowException(JNIEnv *pEnv, jobject pThis) {
logExceptionStatus(pEnv);
const char* clazzName = "java/lang/Exception";
const char* message = "Exception from C++ code!";
throwJavaException(pEnv, clazzName, message);
logExceptionStatus(pEnv);
}
Нас здесь более всего интересует два метода. Первый из них отдает Java-машине команду «кинуть» исключение. Какое именно это будет исключение, определяется именем класса (нечто похожее на механизм рефлексии):
void throwJavaException(JNIEnv* pEnv, const char* pClazzName, const char* pMessage) {
jclass clazz = pEnv->FindClass(pClazzName);
if (clazz) {
pEnv->ThrowNew(clazz, pMessage);
}
pEnv->DeleteLocalRef(clazz);
}
Как мы можем видеть в качестве pClazzName
передается строка с именем класса исключения, например такая "java/lang/Exception"
. Проверка if (clazz)
равносильна проверке if (clazz != NULL)
, и нужна потому что JVM при плохом раскладе может не вернуть нужный объект при вызове FindClass
, произойти это может, например, в случае нехватики памяти. Вызов метода DeleteLocalRef
нужен для удаления локальной ссылки на Java-объект, число которых ограниченно.
Теперь рассмотрим интересный метод ExceptionCheck
в составе метода logExceptionStatus
:
void logExceptionStatus(JNIEnv* pEnv) {
jboolean isException = pEnv->ExceptionCheck();
const char* logMessageTemplate = "Exception occured: %s";
char logMessage[64];
sprintf(logMessage, logMessageTemplate, (isException ? "true" : "false"));
LOG_D(logMessage);
}
Вызвав этот метод мы можем спросит у JVM «а было ли исключение?». Для иллюстрации приведу строчки, записанные системой в лог:
09-06 22:49:13.488: D/Native code(1485): Exception occured: false
09-06 22:49:13.488: D/Native code(1485): Exception occured: true
Помимо того, что это удобно, это означает что:
Поэтому даже если произошло исключение, в нативном коде у вас все еще будет возможность освободить ресурсы или выполнить иную работу для корректного завершения.
Жизнь после исключение: что можно и нельзя делать после env->ThrowNew()
Полный список методов, доступных после возбуждения исключения можной найти тут.
Более подробно об этом можеть быть когда-нибудь:)
Исчерпывающее руководство по JNI от Sun.
Спасибо за внимание, всего вам нативного Исходники тут.