40

Dự án tôi hiện đang làm việc yêu cầu tôi mã hóa phần Android của triển khai chương trình nền tảng chéo.Ghi ngoại lệ được ném từ mã gốc chạy trên Android

Một bộ chức năng cốt lõi được xây dựng và đưa vào ứng dụng của tôi thông qua android-ndk. Tôi đã thấy rằng bất kỳ ngoại lệ/sự cố nào xảy ra trong mã gốc chỉ được báo cáo ngay bây giờ và tốt nhất là tốt nhất. Khi xảy ra lỗi, tôi nhận được một trong các hành vi sau:

  • Sự cố xảy ra và được ghi vào tệp nhật ký. Chương trình biến mất (không có dấu hiệu được đưa ra trên thiết bị là tại sao đột nhiên các ứng dụng không còn ở đó).
  • Không có stacktrace/dump hoặc chỉ báo khác được đưa ra rằng mã gốc đã bị lỗi. Chương trình biến mất.
  • Mã java bị lỗi với một NullPointerException (thường ở cùng một vị trí cho mỗi ngoại lệ mã gốc là một nỗi đau lớn). Thường khiến tôi mất nhiều thời gian cố gắng gỡ lỗi vì sao mã Java đã ném một lỗi chỉ để khám phá mã Java là tốt. & lỗi mã gốc đã bị che khuất hoàn toàn.

Tôi dường như không tìm được cách nào để "cách ly" mã của tôi chống lại các lỗi xảy ra trong mã gốc. Các câu lệnh try/catch được bỏ qua. Ngoài ra khi mã của tôi bị đánh lừa là thủ phạm, tôi thậm chí không có cơ hội cảnh báo người dùng hơn là một lỗi đã xảy ra.

Ai đó có thể vui lòng giúp tôi hiểu cách phản hồi tình trạng mã nguồn gốc bị lỗi không?

+0

Kiểm tra đơn vị, nhật ký ... Các lựa chọn thay thế duy nhất tôi biết (nhưng tôi biết faaar từ mọi thứ, vì vậy hãy xem thêm :)) – Warpzit

+0

Bạn có kiểm soát được mã gốc không? Hoặc chỉ là phía Java? –

+0

Chỉ có lớp mã gốc hàng đầu, tức là lớp JNI Binder. – Graeme

Trả lời

42

Tôi từng có cùng một vấn đề, đúng là trong android (bên trong bất kỳ máy ảo nào nói chung khi thực thi mã gốc) nếu bạn ném ngoại lệ C++ và cái này không bị bắt, VM chết (Nếu tôi hiểu chính xác , Tôi nghĩ đó là vấn đề của bạn). Giải pháp tôi đã sử dụng là bắt bất kỳ ngoại lệ nào trong C++ và ném một ngoại lệ java thay vì sử dụng JNI. Mã tiếp theo nó là một ví dụ đơn giản về giải pháp của tôi. Trước hết, bạn có một phương thức JNI bắt một ngoại lệ C++ và sau đó trong mệnh đề try, ngoại lệ Java được chú thích.

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); 
} 

Lưu ý rằng sau khi ThrowNew, phương thức gốc không đột ngột chấm dứt tự động. Tức là, kiểm soát luồng trở về phương thức gốc của bạn và ngoại lệ mới đang chờ xử lý tại thời điểm này. Ngoại lệ sẽ được ném sau khi phương pháp JNI của bạn kết thúc.

Tôi hy vọng đó là giải pháp bạn đang tìm kiếm.

+0

Tôi đã hy vọng điều gì đó nhiều hơn một chút ... bao gồm. Nhưng sau khi tiền thưởng này dường như là tốt như nó được cho cách điện mã Java từ các lỗi mã Native. – Graeme

+3

Trong khi câu trả lời này là rất tốt cho việc bắt ném ngoại lệ C++ nó không đối phó với các lỗi SIGNAL (bao gồm cả phiên bản C của một NullPointerException). Đây là một bài đăng tuyệt vời mà, ở trên, sẽ có thể ghim chặt chẽ báo cáo lỗi trong ứng dụng gốc của bạn: http://stackoverflow.com/a/1789879/726954 – Graeme

+1

Câu trả lời này chi tiết hơn một chút và có thể tốt hơn cho nhu cầu của bạn: http://stackoverflow.com/a/12014833 –

0

Bạn đã xem xét việc bắt ngoại lệ này và sau đó gói nó trong một ngoại lệ thời gian chạy, chỉ để có được nó cao hơn trong ngăn xếp?

Tôi đã sử dụng 'hack' tương tự trong SCJD. Nói chung NPE chỉ ra một lỗi về phía bạn, nhưng nếu bạn bị thuyết phục rằng bạn không làm gì sai, thì chỉ cần làm một tài liệu tốt RuntimeException giải thích rằng ngoại lệ được sử dụng để bong bóng Ngoại lệ. Sau đó unwrap nó và kiểm tra nếu ví dụ của NPE và đối phó với nó như là ngoại lệ của riêng bạn.

Nếu kết quả là dữ liệu sai, thì bạn không có tùy chọn nào khác, nhưng để đến gốc của nó.

+0

Bạn có nghĩa là, bạn nghĩ rằng tôi nên tuyên truyền ngoại lệ lên bằng cách ném một 'RuntimeException'? Khi gỡ lỗi ứng dụng, NPE sẽ được ném khi một .set() (ví dụ) được gọi trên một biến mà bạn có thể thấy thông qua trình gỡ rối và có thể báo cáo từ một 'Log.v()' trực tiếp trước tệp .set() được gọi là. – Graeme

+0

Tôi có nghĩa là nếu bạn không thể truy cập thư mục gốc của nó vì nó không phải là API của riêng bạn. Sau đó quấn nó cho chính mình trong một ngoại lệ mà bạn hoàn toàn nhận thức được như 'BubbleException', bạn có thể kiểm tra cao hơn trong ngăn xếp của bạn cho ngoại lệ này và đối phó với nó như là của riêng bạn. Nói chung, mặc dù NPE cho biết có một null ở đâu đó, nhưng nếu bạn nghĩ rằng gốc của nó thấp hơn trong ngăn xếp (có thể là mã bạn đang sử dụng), hoặc là mương mã, hoặc ném nó trong RuntimeException của riêng bạn để không làm phiền các ứng dụng API của bạn với một ngoại lệ mà bạn không thể giải thích cho mình. – thejartender

+0

Điều tôi đang nói là mã gốc không "ném" và Ngoại lệ, nó chết theo một cách hoàn toàn khác mà không thể bị bắt bằng 'try' /' catch'. Báo cáo của 'NPE' tôi nghĩ là một tác dụng phụ của mã nguồn gốc chết và không liên quan trực tiếp đến vụ tai nạn theo bất kỳ cách nào. – Graeme

5

CHỈNH SỬA:   Xem thêm this more elegant answer.


Dưới cơ chế dựa trên một C preprocessor macro mà tôi đã thực hiện thành công trong một lớp JNI.

Macro trên CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION chuyển đổi ngoại lệ C++ thành ngoại lệ Java.

Thay thế mypackage::Exception bằng ngoại lệ C++ của riêng bạn. Nếu bạn chưa xác định my.group.mypackage.Exception tương ứng trong Java, hãy thay thế "my/group/mypackage/Exception" theo "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");   \ 
    } 

File Java_my_group_mypackage_example.cpp sử dụng macro trên:

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 
} 

Chỉ cần cung cấp thông tin hoặc sự tò mò, tôi cung cấp dưới đây mã Java tương ứng (file example.java). Lưu ý rằng "my-DLL-name" là mã C/C++ ở trên được biên dịch dưới dạng DLL ("my-DLL-name" không có phần mở rộng ".dll"). Điều này cũng hoạt động hoàn hảo bằng cách sử dụng thư viện chia sẻ 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 
    } 
} 

Thứ nhất, tạo example.class từ example.java (sử dụng javac hoặc yêu thích IDE hoặc maven của bạn ...). Thứ hai, tạo tập tin tiêu đề C/C++ Java_my_group_mypackage_example.h từ example.class sử dụng javah.

+0

Tôi đang mong đợi ';' trước khi 'bắt' lỗi – itsrajesh4uguys

+0

Hi @ itsrajesh4uguys Tôi nghĩ rằng vấn đề của bạn chỉ là trước khi sử dụng 'CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION'. Để bản địa hóa dòng, bạn có thể thay thế 'CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION' bằng mã tương ứng (không có' \ 'ở cuối mỗi dòng). Chúc may mắn, chúc mừng ;-) – olibre

Các vấn đề liên quan