web-dev-qa-db-ja.com

Androidで実行されているネイティブコードからスローされた例外をキャッチする

現在取り組んでいるプロジェクトでは、クロスプラットフォームプログラム実装のAndroid部分をコーディングする必要があります。

Android-ndkを介してコア機能セットが構築され、アプリに含まれています。ネイティブコードで発生する例外/クラッシュは、たまにしか報告されないことがわかりました。エラーが発生すると、次のいずれかの動作が発生します。

  • スタックトレース/メモリダンプが発生し、ログファイルに書き込まれます。プログラムが表示されなくなります(デバイスに突然アプリが表示されなくなった理由は表示されません)。
  • ネイティブコードがクラッシュしたことを示すスタックトレース/ダンプなどの表示はありません。プログラムが消えます。
  • JavaコードはNullPointerExceptionでクラッシュします(通常はネイティブコードの例外ごとに同じ場所にあり、これは大きな苦痛です)。 Javaコードは、Javaコードは問題ありませんが、ネイティブコードエラーは完全にマスクされています。

ネイティブコードで発生するエラーに対してコードを「隔離」する方法を見つけることができないようです。 try/catchステートメントは無視されます。私のコードが犯人であるということを除けば、エラーが発生したことをユーザーに警告する機会すらありません。

ネイティブコードがクラッシュする状況に対応する方法について誰かが私を助けてくれますか?

50
Graeme

私は同じ問題を抱えていましたが、C++例外をスローすると、Android(ネイティブコードを実行するときに一般的にVM内)これはキャッチされず、VMが死にます(正しく理解できれば、それはあなたの問題だと思います)。私が採用した解決策は、C++で例外をキャッチしてJava例外。次のコードは、私のソリューションの簡単な例です。まず、C++例外をキャッチするJNIメソッドがあり、次に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メソッドが終了すると、例外がスローされます。

それがあなたが探している解決策だったことを願っています。

48
javier-sanz

EDIT:このよりエレガントな答え も参照してください。


以下のメカニズムは、 [〜#〜] jni [〜#〜] レイヤー内で正常に実装された Cプリプロセッサマクロ に基づいています。

上記のマクロCATCH_CPP_EXCEPTION_AND_THROW_Java_EXCEPTIONは、C++例外をJava例外に変換します。

mypackage::Exceptionを独自のC++例外に置き換えます。 Javaで対応するmy.group.mypackage.Exceptionを定義していない場合は、"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 ...を使用します)。次に、javahを使用してJava_my_group_mypackage_example.hからC/C++ヘッダーファイルexample.classを生成します。

5
olibre

この例外をキャッチしてから、ランタイム例外にラップして、スタックの上位に上げることを検討しましたか?

SCJDでも同様の「ハック」を使用しました。一般的に、NPEはあなたの側でエラーを示しますが、何も悪いことをしていないと確信している場合は、例外をバブリングするために例外が使用されることを説明する十分に文書化されたRuntimeExceptionを作成します。次に、それをアンラップして、たとえばNPEのインスタンスをテストし、独自の例外として処理します。

誤ったデータが発生する場合、他のオプションはありませんが、そのルートに到達します。

0
thejartender