Постановка задачи

Если в вашем приложении есть части написанные при помощи 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

Помимо того, что это удобно, это означает что:

После возбуждение исключения из нативного кода выполнение нативного кода продолжается. Но использовать ссылку на JNIEnv полноценно уже нельзя, только некоторые методы.

Поэтому даже если произошло исключение, в нативном коде у вас все еще будет возможность освободить ресурсы или выполнить иную работу для корректного завершения.

Жизнь после исключение: что можно и нельзя делать после env->ThrowNew()

Полный список методов, доступных после возбуждения исключения можной найти тут.

Более подробно об этом можеть быть когда-нибудь:)

Исчерпывающее руководство по JNI от Sun.

Спасибо за внимание, всего вам нативного :) Исходники тут.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *