Ловля исключений, генерируемых из собственного кода, работающего на Android - PullRequest
49 голосов
/ 07 декабря 2011

Проект, над которым я сейчас работаю, требует, чтобы я запрограммировал Android-часть реализации кроссплатформенной программы.

Основной набор функций встроен и включен в мое приложение через android-ndk. Я обнаружил, что о любых исключениях / сбоях, происходящих в нативном коде, в лучшем случае сообщается только время от времени. При возникновении ошибки я получаю одно из следующих действий:

  • Происходит отслеживание стека / памяти и записывается в файл журнала. Программа исчезает (на устройстве не указано, почему приложение внезапно перестало существовать).
  • Нет отслеживания / дампа стека или других указаний на то, что собственный код вышел из строя. Программа исчезает.
  • Java-код аварийно завершается с NullPointerException (обычно в одном и том же месте на исключение нативного кода, что вызывает огромную боль). Обычно я трачу много времени, пытаясь отладить, почему код Java выдал ошибку только для того, чтобы обнаружить, что код Java в порядке, а ошибка собственного кода полностью замаскирована.

Кажется, я не могу найти способ "изолировать" мой код от ошибок, которые происходят в нативном коде. Операторы try / catch полностью игнорируются. Помимо того, что мой код считается пальцем, я даже не имею возможности предупредить пользователя о том, что произошла ошибка.

Может кто-нибудь помочь мне с тем, как реагировать на ситуацию сбоя нативного кода?

Ответы [ 3 ]

47 голосов
/ 13 декабря 2011

Раньше у меня была такая же проблема, это правда, что в Android (внутри любой виртуальной машины вообще при выполнении собственного кода), если вы генерируете исключение C ++, а это не перехватывается, VM умирает (Если я правильно понял Я думаю это твоя проблема). Решение, которое я принял, состояло в том, чтобы перехватить любое исключение в C ++ и выдать исключение Java вместо использования JNI. Следующий код - это упрощенный пример моего решения. Прежде всего, у вас есть метод JNI, который перехватывает исключение C ++, а затем в предложении try аннотируется исключение 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("company/com/YourException");

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

    env->ThrowNew(c, msg);
}

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

Я надеюсь, что это было решение, которое вы ищете.

5 голосов
/ 08 марта 2013

РЕДАКТИРОВАТЬ: & ensp; Смотрите также этот более элегантный ответ .


Механизм ниже основан на макросе препроцессора C , который я успешно реализовал в слое JNI .

Приведенный выше макрос CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION преобразует исключения C ++ в исключения Java.

Замените mypackage::Exception вашим собственным исключением C ++. Если вы не определили соответствующий my.group.mypackage.Exception в Java, замените "my/group/mypackage/Exception" на "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");          \
  }

Файл Java_my_group_mypackage_example.cpp с использованием приведенного выше макроса:

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
}

Просто для информации или любопытства, я приведу ниже соответствующий код Java (файл example.java). Обратите внимание, что «my-DLL-name» - это приведенный выше код C / C ++, скомпилированный как DLL («my-DLL-name» без расширения «.dll»). Это также прекрасно работает при использовании общей библиотеки 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
  }
}

Сначала сгенерируйте example.class из example.java (используя javac или ваш любимый IDE или maven ...). Во-вторых, сгенерируйте заголовочный файл C / C ++ Java_my_group_mypackage_example.h из example.class, используя javah.

0 голосов
/ 12 декабря 2011

Рассматривали ли вы перехват этого исключения, а затем заключили его в исключение времени выполнения, просто чтобы поднять его выше в стеке?

Я использовал аналогичный «хак» в SCJD. Обычно NPE указывает на ошибку с вашей стороны, но если вы уверены, что не делаете ничего плохого, просто сделайте хорошо документированный RuntimeException, который объясняет, что исключение используется для всплытия исключения. Затем разверните его и проверьте, например, NPE, и рассматривайте его как свое собственное исключение.

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

...