Android NDK開發之旅14 JNI 異常處理

kpioneer123發表於2018-01-16

###異常處理

異常測試例子:

public native void testException1();

public static void main(String[] args) {

    JniTest test = new JniTest();

    try {
        test.testException();
        System.out.println("程式無法繼續執行1,這句話不會被列印\n");
    } catch (Throwable t) {
        System.out.println("捕獲到JNI丟擲的異常(Throwable),這句話會被列印" + t.getMessage() + "\n");
    }

    System.out.println("程式繼續執行2,這句話會被列印\n");

}
複製程式碼

C程式碼如下:

//異常處理
JNIEXPORT void JNICALL Java_com_test_JniTest_testException1
(JNIEnv * env, jobject jobj){

    jclass clz=  (*env)->GetObjectClass(env, jobj);
    //屬性名字不小心寫錯了,拿到的是空的jfieldID
    jfieldID fid = (*env)->GetFieldID(env, clz, "key1", "Ljava/lang/String;");

    //此處丟擲的異常,Java可以通過Throwable來捕獲

    printf("C can run , this will print");
    //這裡竟然還可以繼續執行
    jstring key =  (*env)->GetObjectField(env, jobj, fid);
    //遇到這句話的時候,C程式Crash了
    char* c_str = (*env)->GetStringUTFChars(env, key, NULL);
    printf("C could not run , this will not print");
}
複製程式碼

通過例子可以知道,JNI層自己丟擲的異常是Error型別,Java可以通過Throwable或者Error來捕獲得到,捕獲異常後Java程式碼可以繼續執行下去。

#####為了確保Java、C/C++程式碼可以正常執行下去,需要:

在JNI層手動清空異常資訊(ExceptionClear),保證程式碼可以執行。 補救措施保證C/C++程式碼繼續執行。 例如:

//異常處理
JNIEXPORT void JNICALL Java_com_test_JniTest_testException1
(JNIEnv * env, jobject jobj){
    jclass clz = (*env)->GetObjectClass(env, jobj);
    //屬性名字不小心寫錯了,拿到的是空的jfieldID
    jfieldID fid = (*env)->GetFieldID(env, clz, "key1", "Ljava/lang/String;");

    jthrowable err = (*env)->ExceptionOccurred(env);
    if (err != NULL){
        //手動清空異常資訊,保證Java程式碼能夠繼續執行
        (*env)->ExceptionClear(env);
        //提供補救措施,例如獲取另外一個屬性
        fid = (*env)->GetFieldID(env, clz, "key", "Ljava/lang/String;");
    }


    jstring key = (*env)->GetObjectField(env, jobj, fid);
    char* c_str = (*env)->GetStringUTFChars(env, key, NULL);
}
複製程式碼

測試程式碼如下:

public static void main(String[] args) {

    JniTest test = new JniTest();

    try {
        test.testException();
        System.out.println("程式沒有異常,這句話會被列印\n");
    } catch (Exception e) {
        System.out.println("沒有捕獲到JNI丟擲的異常,這句話不會被列印" + e.getMessage() + "\n");
    }

    System.out.println("程式繼續執行,這句話會被列印\n");

}
複製程式碼

使用者可以手動通過ThrowNew函式丟擲異常,同樣可以被Java程式碼捕獲:

//異常處理
JNIEXPORT void JNICALL Java_com_test_JniTest_testException
(JNIEnv * env, jobject jobj){
    jclass clz = (*env)->GetObjectClass(env, jobj);
    //屬性名字不小心寫錯了,拿到的是空的jfieldID
    jfieldID fid = (*env)->GetFieldID(env, clz, "key1", "Ljava/lang/String;");

    jthrowable err = (*env)->ExceptionOccurred(env);
    if (err != NULL){
        //手動清空異常資訊,保證Java程式碼能夠繼續執行
        (*env)->ExceptionClear(env);
        //提供補救措施,例如獲取另外一個屬性
        fid = (*env)->GetFieldID(env, clz, "key", "Ljava/lang/String;");
    }


    jstring key = (*env)->GetObjectField(env, jobj, fid);
    char* c_str = (*env)->GetStringUTFChars(env, key, NULL);

    //引數不正確,程式設計師自己丟擲異常,可以在Java中捕獲
    if (_stricmp(c_str,"efg") != 0){
        jclass err_clz = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
        (*env)->ThrowNew(env, err_clz, "key value is invalid!");
    }
}
複製程式碼

測試程式碼如下:

public static void main(String[] args) {

    JniTest test = new JniTest();

    try {
        test.testException();
        System.out.println("JNI手動丟擲了異常,Java不會繼續執行,這句話不會被列印\n");
    } catch (Exception e) {
        System.out.println("捕獲到JNI手動丟擲的異常,這句話會被列印:" + e.getMessage() + "\n");
    }

    System.out.println("程式繼續執行,這句話會被列印\n");

}
複製程式碼

####異常處理總結

JNI自己丟擲的異常,是Error型別,Java可以通過Throwable或者Error來捕獲得到,捕獲異常後Java程式碼可以繼續執行下去。在C層可以清空(ExceptionClear),保證try中的程式碼Java程式碼繼續執行,並且最好要提供補救措施,確保JNI層程式碼正常繼續執行。 使用者通過ThrowNew手動丟擲的異常,同樣可以在Java層捕捉得到。

相關文章