40

我目前正在處理的項目需要我編寫跨平臺程序實現的android部分。捕捉從Android上運行的本機代碼拋出的異常

一套核心功能是通過android-ndk構建幷包含在我的應用程序中。我發現在本地代碼中發生的任何異常/崩潰最多隻能被一次又一次地報告。發生錯誤時,出現以下某種行爲:

  • 發生堆棧跟蹤/內存轉儲並將其寫入日誌文件。該程序消失(設備上沒有提示爲什麼突然該應用程序不再存在)。
  • 未給出堆棧跟蹤/轉儲或其他指示,說明本機代碼已崩潰。該程序消失。
  • java代碼與NullPointerException(通常在每個本地代碼異常相同的地方崩潰,這是一個巨大的痛苦)崩潰。通常會導致我花費相當長的時間來試圖調試爲什麼Java代碼拋出錯誤只是爲了發現Java代碼是好的&本機代碼錯誤已被完全屏蔽。

我似乎無法找到任何方法來「隔離」我的代碼與本機代碼中發生的錯誤。 Try/catch語句被完全忽略。除了當我的代碼被指爲罪魁禍首時,我甚至沒有機會警告用戶,而不是發生錯誤。

有人可以幫我解決如何應對崩潰本機代碼的情況?

+0

單元測試,日誌...我知道的唯一選擇(但我知道所有東西都是faaar,所以請進一步看:)) – Warpzit

+0

您是否控制了本機代碼?或者只是Java方面? –

+0

只有本地代碼的最頂層,即JNI活頁夾層。 – Graeme

回答

42

我曾經有過同樣的問題,如果你拋出一個C++異常並且沒有捕獲到這個異常,那麼在android(在執行本機代碼的任何虛擬機中) ,我認爲這是你的問題)。我採用的解決方案是在C++中捕獲任何異常,並拋出一個java異常,而不是使用JNI。下一個代碼是我的解決方案的簡化示例。首先,您有一個捕獲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方法完成後將引發異常。

我希望這是您正在尋找的解決方案。

+0

我希望的東西多一點...包容。但是,在賞金之後,這看起來和從原生代碼崩潰中隔離Java代碼一樣好。 – Graeme

+3

雖然這個答案非常適合捕獲拋出的C++異常,但它不處理SIGNAL錯誤(其中包括C的NullPointerException版本)。這是一個很好的帖子,與上面一樣,應該能夠在你的本地應用程序中緊密地鎖定錯誤報告:http://stackoverflow.com/a/1789879/726954 – Graeme

+1

這個答案有點更詳細,可能更適合您的需求:http://stackoverflow.com/a/12014833 –

0

您是否考慮捕獲此異常,然後將其封裝在運行時異常中,以便在堆棧中將其更高?

我在SCJD中使用了類似的'hack'。通常NPE表示你的錯誤,但如果你確信自己沒有做錯任何事情,那麼只需製作一份有據可查的文件RuntimeException即可解釋異常是用來冒泡例外的。然後打開它並測試NPE的實例,並將其作爲您自己的異常進行處理。

如果它會導致錯誤的數據,那麼你沒有別的選擇,只能到它的根。

+0

你的意思是,你認爲我應該通過拋出'RuntimeException'來傳播異常?調試應用程序時,如果在通過調試器可以看到的變量上調用.set()(例如),並且可以直接在.se​​t()之前從「Log.v()」報告一個.set()叫做。 – Graeme

+0

我的意思是,如果你不能得到它的根源,因爲它不是你自己的API投擲它。然後將它自己包裝在一個你完全知道的異常中,像'BubbleException',然後你可以在堆棧中爲這個異常進行更高級的測試,並將其作爲你自己的處理。通常儘管NPE表示在某處存在null,但如果您認爲它的根在堆棧中較低(可能是您正在使用的代碼),則可以丟棄代碼或將其拋入您自己的RuntimeException中,以免給您的API客戶端帶來不便有一個例外,你無法解釋自己。 – thejartender

+0

我在說的是本機代碼不會「拋出」和異常,它會以一種完全不同的方式死掉,而這種方式不能被'try' /'catch'抓住。我認爲報道的NPE是本地代碼死亡的副作用,並且不以任何方式與崩潰直接相關。 – Graeme

5

編輯:  另請參見this more elegant answer


下面機制是基於一個C preprocessor macro,我已經成功一個JNI層內實現。

上述宏CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION將C++異常轉換爲Java異常。

用您自己的C++異常替換mypackage::Exception。如果您還沒有在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」是上述編譯爲DLL的C/C++代碼(「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.classexample.java(使用javac或者你喜歡的IDE或Maven ...)。其次,使用javahexample.class生成C/C++頭文件Java_my_group_mypackage_example.h

+0

我期待';' 'catch'錯誤之前 – itsrajesh4uguys

+0

Hi @ itsrajesh4uguys我認爲你的問題就在使用'CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION'之前。爲了本地化這條線,你可以用相應的代碼替換'CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION'(每行末尾沒有'\')。祝你好運,歡呼聲;-) – olibre