Cómo capturar excepciones generadas con código nativo que se ejecuta en Android

El proyecto en el que estoy trabajando actualmente requiere que codifique la parte androide de una implementación de programa de plataforma cruzada.

Un conjunto básico de funcionalidad se construye y se incluye en mi aplicación a través de android-ndk . He encontrado que cualquier excepción / caída que ocurre en el código nativo sólo se informa de vez en cuando en el mejor de los casos. Cuando se produce un error, obtengo uno de los siguientes comportamientos:

  • Se produce un volcado de pila / memoria y se escribe en el archivo de registro. El programa desaparece (no se indica en el dispositivo por qué de repente la aplicación ya no existe).
  • No hay stacktrace / dump u otra indicación es dado que el código nativo se ha estrellado. El programa desaparece.
  • El código java se bloquea con una excepción NullPointerException (generalmente en el mismo lugar por excepción de código nativo que es un dolor masivo). Por lo general, hacerme pasar bastante tiempo tratando de depurar por qué el código Java ha lanzado un error sólo para descubrir el código de Java está bien y el error de código nativo ha sido totalmente enmascarado.

No puedo encontrar ninguna manera de "aislar" mi código contra los errores que se producen en el código nativo. Las sentencias Try / catch se ignoran rotundamente. Aparte de cuando mi código es fingered como el culpable ni siquiera tengo la oportunidad de advertir al usuario que se ha producido un error.

¿Puede alguien por favor me ayude en cuanto a cómo responder a la situación de fallar código nativo?

Yo solía tener el mismo problema, es cierto que en android (dentro de cualquier VM en general, al ejecutar código nativo) si se lanza una excepción de C + + y este no se captura, la VM muere (Si he entendido correctamente, creo que es tu problema). La solución que adopté fue capturar cualquier excepción en C + + y lanzar una excepción java en lugar de usar JNI. El siguiente código es un ejemplo simplificado de mi solución. En primer lugar, tiene un método JNI que detecta una excepción de C ++ y, a continuación, en la cláusula try se anota la excepción de Java.

 JNIEXPORT void JNICALL Java_com_MyClass_foo (JNIEnv *env, jobject o,jstring param) { try { // Your Stuff ... } // You can catch std::exception for more generic error handling catch (MyCxxException e) { throwJavaException (env, e.what()); } } void throwJavaException(JNIEnv *env, const char *msg) { // You can put your own exception here jclass c = env->FindClass("java/lang/RuntimeException"); if (NULL == c) { //B plan: null pointer ... c = env->FindClass("java/lang/NullPointerException"); } env->ThrowNew(c, msg); } 

Tenga en cuenta que después de un ThrowNew, el método nativo no termina de forma abrupta automáticamente. Es decir, el flujo de control vuelve a su método nativo y la nueva excepción está pendiente en este momento. La excepción se lanzará después de que su método JNI esté terminado.

Espero que sea la solución que buscas.

EDIT: Véase también esta respuesta más elegante .


Debajo del mecanismo se basa en una macro de preprocesador C que he implementado con éxito dentro de una capa JNI .

La macro anterior CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION convierte las excepciones de C ++ en excepciones de Java.

Reemplace mypackage::Exception por su propia excepción de C ++. Si no ha definido el my.group.mypackage.Exception correspondiente en Java, reemplace "my/group/mypackage/Exception" por "java/lang/RuntimeException" .

 #define CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION \ \ catch (const mypackage::Exception& e) \ { \ jclass jc = env->FindClass("my/group/mypackage/Exception"); \ if(jc) env->ThrowNew (jc, e.what()); \ /* if null => NoClassDefFoundError already thrown */ \ } \ catch (const std::bad_alloc& e) \ { \ /* OOM exception */ \ jclass jc = env->FindClass("java/lang/OutOfMemoryError"); \ if(jc) env->ThrowNew (jc, e.what()); \ } \ catch (const std::ios_base::failure& e) \ { \ /* IO exception */ \ jclass jc = env->FindClass("java/io/IOException"); \ if(jc) env->ThrowNew (jc, e.what()); \ } \ catch (const std::exception& e) \ { \ /* unknown exception */ \ jclass jc = env->FindClass("java/lang/Error"); \ if(jc) env->ThrowNew (jc, e.what()); \ } \ catch (...) \ { \ /* Oops I missed identifying this exception! */ \ jclass jc = env->FindClass("java/lang/Error"); \ if(jc) env->ThrowNew (jc, "unidentified exception"); \ } 

El archivo Java_my_group_mypackage_example.cpp utiliza la macro anterior:

 JNIEXPORT jlong JNICALL Java_my_group_mypackage_example_function1 (JNIEnv *env, jobject object, jlong value) { try { /* ... my processing ... */ return jlong(result); } CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION return 0; } JNIEXPORT jstring JNICALL Java_my_group_mypackage_example_function2 (JNIEnv *env, jobject object, jlong value) { try { /* ... my processing ... */ jstring jstr = env->NewStringUTF("my result"); return jstr; } CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION return 0; } JNIEXPORT void JNICALL Java_my_group_mypackage_example_function3 (JNIEnv *env, jobject object, jlong value) { try { /* ... my processing ... */ } CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION } 

Solo por información o curiosidad, proveo debajo del código Java correspondiente (archivo example.java ). Tenga en cuenta el "nombre de my-DLL-name " es el código C / C ++ anterior compilado como un DLL (" my-DLL-name " sin la extensión " .dll "). Esto también funciona perfectamente con Linux / Unix biblioteca compartida *.so .

 package my.group.mypackage; public class Example { static { System.loadLibrary("my-DLL-name"); } public Example() { /* ... */ } private native int function1(int); //declare DLL functions private native String function2(int); //using the keyword private native void function3(int); //'native' public void dosomething(int value) { int result = function1(value); String str = function2(value); //call your DLL functions function3(value); //as any other java function } } 

Primero, genera example.class desde example.java (usando javac o tu IDE favorito o maven …). Segundo, genere el archivo de encabezado C / C ++ Java_my_group_mypackage_example.h desde example.class usando javah .

¿Ha considerado capturar esta excepción y luego envolverla en una excepción de ejecución, sólo para subirla más arriba en la pila?

Utilicé un "hack" similar en SCJD. En general, NPE indica un error de su parte, pero si está convencido de que no está haciendo nada malo, simplemente haga una RuntimeException bien documentada que explique que la excepción se utiliza para burbujear la excepción. A continuación, desenrollarlo y probar si por ejemplo de NPE y tratar con él como su propia Excepción.

Si va a resultar en datos erróneos, entonces no tienes otra opción, pero para llegar a la raíz de la misma.

Existe la posibilidad de capturar excepciones por UncaughtExceptionHandler , pero no estoy seguro de si capturará excepciones de código nativo

Acabo de terminar ACRA y la integración de breakpad : https://github.com/4ntoine/Acra-breakpad

Echa un vistazo a la lib y demo de la aplicación. Espero que esta integración de dos herramientas maduras será útil para alguien.

  • Android, NetworkInfo.getTypeName (), NullpointerException
  • contexto android nullpointerexception
  • Excepción de puntero nulo iniciando IntentService
  • NullPointerException en SharedPreferences Android
  • Android ActivityOptions.isReturning () NullPointerException
  • Puntero nulo Excepción - findViewById ()
  • Cámara Android: archivo vacío en el método onActivityResult
  • Cómo serializar el valor nulo cuando se utiliza Parcelable interfaz
  • Excepción Java del puntero nulo
  • Java.lang.NullPointerException: Intenta invocar el método virtual 'ActionBar.setNavigationMode (int)' en una referencia de objeto nulo
  • Excepción de puntero nulo de Android para las preferencias compartidas
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.