40

Projekt, nad którym obecnie pracuję, wymaga, abym zakodował część Androida związaną z implementacją programu na wielu platformach.Łapanie wyjątków wyrzuconych z natywnego kodu działającego pod kontrolą Androida

Podstawowy zestaw funkcji został zbudowany i dołączony do mojej aplikacji przez android-ndk. Odkryłem, że każdy wyjątek/awaria występująca w natywnym kodzie jest zgłaszana tylko co jakiś czas. Gdy wystąpi błąd, otrzymuję jedno z następujących zachowań:

  • Wystąpienie stosu/zrzutu pamięci występuje i jest zapisywane w pliku dziennika. Program znika (brak wskazania na urządzeniu, dlaczego nagle aplikacja już nie istnieje).
  • Brak śledzenia stosu/zrzutu lub inne wskazanie, że natywny kod uległ awarii. Program zniknie.
  • Kod java ulega awarii z NullPointerException (zwykle w tym samym miejscu na wyjątek kodu natywnego, który jest ogromny ból). Zwykle powoduje to, że przez dłuższy czas próbuję debugować, dlaczego kod Java rzucił błąd tylko w celu odkrycia, że ​​kod Java jest w porządku. & natywny błąd kodu został całkowicie zamaskowany.

Nie mogę znaleźć sposobu na "odizolowanie" mojego kodu od błędów występujących w kodzie rodzimym. Wyrażenia try/catch są zdecydowanie ignorowane. Poza tym, kiedy mój kod jest dotknięty jako winowajca, nie mam nawet możliwości ostrzeżenia użytkownika, niż wystąpił błąd.

Czy ktoś może mi pomóc, jak zareagować na awarię natywnego kodu?

+0

Testy jednostkowe, logi ... Jedyne alternatywy, które znam (ale znam wszystko od wszystkiego, więc proszę spojrzeć dalej)) – Warpzit

+0

Czy w ogóle kontrolujesz natywny kod? A może po stronie Java? –

+0

Tylko najwyższa warstwa kodu natywnego, tj. Warstwa JNI Binder. – Graeme

Odpowiedz

42

Miałem ten sam problem, to prawda, że ​​w Androidzie (w każdej maszynie wirtualnej w ogóle podczas wykonywania natywnego kodu), jeśli rzucisz wyjątek C++, a ten nie zostanie złapany, VM umiera (Jeśli rozumiem poprawnie , Myślę, że to twój problem). Rozwiązaniem, które zastosowałem, było wychwycenie dowolnego wyjątku w C++ i wyrzucenie wyjątku java zamiast używania JNI. Następny kod to uproszczony przykład mojego rozwiązania. Przede wszystkim masz metodę JNI, która przechwytuje wyjątek C++, a następnie w klauzuli try wyjątek Java jest adnotowany.

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("company/com/YourException"); 

    if (NULL == c) 
    { 
     //B plan: null pointer ... 
     c = env->FindClass("java/lang/NullPointerException"); 
    } 

    env->ThrowNew(c, msg); 
} 

Należy pamiętać, że po ThrowNew natywna metoda nie kończy się automatycznie. Oznacza to, że przepływ sterowania powraca do Twojej natywnej metody, a nowy wyjątek oczekuje na ten moment. Wyjątek zostanie zgłoszony po zakończeniu metody JNI.

Mam nadzieję, że było to rozwiązanie, którego szukasz.

+0

Miałem nadzieję na coś trochę więcej ... obejmując. Ale po bounty wygląda na to, że jest tak dobry, jak izolowanie kodu Java od awarii kodu Native. – Graeme

+3

Chociaż ta odpowiedź jest świetna do przechwytywania rzucanych wyjątków C++, nie zajmuje się błędami SIGNAL (które zawierają wersję C wyjątku NullPointerException). Jest to świetny post, który, z góry, powinien być w stanie ciasno zgłaszać zgłaszanie błędów w twoich natywnych aplikacjach: http://stackoverflow.com/a/1789879/726954 – Graeme

+1

Ta odpowiedź jest trochę bardziej szczegółowa i może być lepsza dla twoje potrzeby: http://stackoverflow.com/a/12014833 –

0

Czy rozważałeś wyłapanie tego wyjątku, a następnie jego zawinięcie w wyjątku środowiska wykonawczego, aby uzyskać wyższy poziom w stosie?

Użyłem podobnego "hacka" w SCJD. Ogólnie NPE wskazuje na błąd z Twojej strony, ale jeśli jesteś przekonany, że nie robisz niczego złego, po prostu dobrze udokumentowane RuntimeException, który wyjaśnia, że ​​wyjątek jest używany do bańki Wyjątek. Następnie rozwiń go i sprawdź, czy na przykład NPE i potraktuj jako własny wyjątek.

Jeśli spowoduje to błędne dane, to nie masz innej opcji, ale dostać się do jej korzenia.

+0

Masz na myśli, myślisz, że powinienem propagować wyjątek, rzucając 'RuntimeException'? Podczas debugowania aplikacji zostanie wywołany NPE, gdy wywoływana jest funkcja .set() (na przykład) na zmiennej, którą można zobaczyć za pomocą debuggera i można ją zgłosić z 'Log.v()' bezpośrednio przed .set() jest nazywany. – Graeme

+0

Mam na myśli to, że jeśli nie możesz dostać się do katalogu głównego, ponieważ nie jest to twój własny API, który go rzuca. Następnie zapakuj to dla siebie w wyjątku, że jesteś w pełni świadomy jak "BubbleException", który możesz przetestować wyżej w swoim stosie dla tego wyjątku i traktować go jako swój własny. Zwykle NPE wskazuje, że gdzieś jest pusta, ale jeśli uważasz, że to root jest niższy w stosie (być może kod, którego używasz), to albo porzuć kod, albo wyrzuć go w wyjątkowy RuntimeException, aby nie przeszkadzać klientom API z Wyjątkiem, którego sam nie możesz wyjaśnić. – thejartender

+0

To, co mówię, to natywny kod, który nie "wyrzuca" i wyjątek, umiera w zupełnie inny sposób, który nie może zostać przechwycony przez 'try' /' catch'. Zgłoszone przez "NPE" są moim zdaniem efektem ubocznym umierania kodu natywnego i nie są w żaden sposób związane bezpośrednio z awarią. – Graeme

5

EDYTOWANIE:   Zobacz także this more elegant answer.


Poniżej mechanizm opiera się na C preprocessor macro że z powodzeniem wdrożony w ciągu JNI warstwy.

Powyższe makro CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION konwertuje wyjątki C++ do wyjątków Java.

Wymień mypackage::Exception według własnego wyjątku C++. Jeśli nie zdefiniowałeś odpowiedniego my.group.mypackage.Exception w Javie, zastąp "my/group/mypackage/Exception" przez "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");   \ 
    } 

Plik Java_my_group_mypackage_example.cpp stosując powyższą makro:

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 
} 

Tylko dla informacji i ciekawości dostarczyć poniżej odpowiedniego kodu Java (plik example.java). Uwaga: "my-DLL-name" to powyższy kod C/C++ skompilowany jako biblioteka DLL ("my-DLL-name" bez rozszerzenia ".dll"). Działa to również doskonale przy użyciu biblioteki współdzielonej Linux/Unix *.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 
    } 
} 

Najpierw wygenerować example.class z example.java (używając javac lub ulubionego IDE lub Maven ...). Po drugie, wygeneruj plik nagłówkowy C/C++ Java_my_group_mypackage_example.h z example.class, używając javah.

+0

Oczekuję ";" przed "catch" error – itsrajesh4uguys

+0

Hi @ itsrajesh4uguys Myślę, że twój problem jest tuż przed użyciem "CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION". Aby zlokalizować linię, możesz zamienić "CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION" na odpowiedni kod (bez "\" na końcu każdej linii). Powodzenia, okrzyki ;-) – olibre

Powiązane problemy