★10.異常處理

簡(jiǎn)述

  • 寫(xiě) JNI 代碼時(shí),需要時(shí)刻考慮每一個(gè) JNI函數(shù) 可能拋出的異常。
  • 在原生代碼中,一旦發(fā)生異常,需要馬上處理。

簡(jiǎn)單示例

public class CatchThrow {
    static {
        System.loadLibrary("CatchThrow");
    }

    public static void main(String args[]) {
        CatchThrow c = new CatchThrow();
        try {
            c.doit();
        } catch (Exception e) {
            System.out.println("In Java:\n\t" + e);
        }
    }

    private native void doit() throws IllegalArgumentException;

    private void callback() throws NullPointerException {
        throw new NullPointerException("CatchThrow.callback");
    }
}
JNIEXPORT void JNICALL Java_CatchThrow_doit(JNIEnv * env, jobject obj) {
    jclass cls = (* env)->GetObjectClass(env, obj);
    jmethodID mid = (* env)->GetMethodID(env, cls, "callback", "()V");
    if (mid == NULL) {
        return;
    }

    // 注意此調(diào)用會(huì)拋出異常
    (* env)->CallVoidMethod(env, obj, mid);

    // 嘗試獲取已拋出的異常
    jthrowable exc = (* env)->ExceptionOccurred(env);

    // 通過(guò)此處的判斷,檢查是否真的拋出了異常
    if (exc) {
        // 輸出異常描述
        (* env)->ExceptionDescribe(env);

        // 清除異常
        (* env)->ExceptionClear(env);

        // 創(chuàng)建新的異常
        jclass newExcCls = (* env)->FindClass(env, "java/lang/IllegalArgumentException");
        if (newExcCls == NULL) {
            return;
        }

        // 拋出新的異常取代舊的異常
        (* env)->ThrowNew(env, newExcCls, "thrown from C code");
    }
}

示例解說(shuō)

  • 示例演示了如何聲明一個(gè)可能拋出異常的 原生方法
  • 在原生代碼中,通過(guò)ThrowNew()拋出異常時(shí),不會(huì)馬上中斷執(zhí)行流程。若在 Java 代碼中拋出異常則會(huì)馬上中斷執(zhí)行流程,轉(zhuǎn)到最近的try/catch塊執(zhí)行代碼。

函數(shù)解說(shuō)

  • ExceptionOccurred():若有異常,則返回一個(gè)jthrowable對(duì)象,否則返回NULL。
  • ExceptionCheck():若有異常,則返回JNI_TRUE,否則返回JNI_FALSE。
  • ExceptionDescribe():若有異常,輸出異常描述,相當(dāng)于 Java 代碼中的Exception.printStackTrace()。
  • ExceptionClear():清除隊(duì)列中的異常,后續(xù)代碼不會(huì)在獲取到。
  • ThrowNew():拋出異常。

異常檢查

第一種

簡(jiǎn)介

  • 大多數(shù) JNI函數(shù) 會(huì)直接通過(guò)返回值(如返回NULL)來(lái)說(shuō)明是否遇到了錯(cuò)誤,同時(shí)也暗示了此線程的隊(duì)列中有待處理的異常。

簡(jiǎn)單示例

public class Window {
    static native void initIDs();

    static {
        initIDs();
    }

    long handle;
    int length;
    int width;
}
jfieldID FID_Window_handle;
jfieldID FID_Window_length;
jfieldID FID_Window_width;

JNIEXPORT void JNICALL Java_Window_initIDs(JNIEnv * env, jclass classWindow) {
    // 即便可以確保Window類中有這些字段,也必須要檢查,因?yàn)槿匀豢赡軙?huì)因?yàn)閮?nèi)存不足而失敗
    FID_Window_handle = (* env)->GetFieldID(env, classWindow, "handle", "J");
    if (FID_Window_handle == NULL) {
        return;
    }

    FID_Window_length = (* env)->GetFieldID(env, classWindow, "length", "I");
    if (FID_Window_length == NULL) {
        return;
    }

    FID_Window_width = (* env)->GetFieldID(env, classWindow, "width", "I");
    // 此處沒(méi)有檢測(cè)的必要,無(wú)論成功與否都是直接return
}

第二種

簡(jiǎn)介

  • 有些 JNI函數(shù) 的返回值無(wú)法表明錯(cuò)誤是否發(fā)生,因?yàn)檫@類 JNI函數(shù) 的返回值可能另作他用,如CallIntMethod(),此時(shí)可以通過(guò)ExceptionOccurred()ExceptionCheck()來(lái)檢測(cè)。

簡(jiǎn)單示例

public class Fraction {
    public int floor() {
        return (int) Math.floor((double) over / under);
    }

    int over, under;
}
void Java_Fraction_floor(JNIEnv * env, jobject fraction) {
    // ...
    jint floor = (* env)->CallIntMethod(env, fraction, MID_Fraction_floor);

    // 檢查是否有異常
    if ((* env)->ExceptionCheck(env)) {
        // 處理異常
        return;
    }
    // ...
}

異常處理

  • 有兩種處理異常的方法:
    1. 立即返回,讓異常傳遞至 Java 代碼。
    2. 使用ExceptionClear()清除異常,然后處理異常
  • 在調(diào)用一些 JNI函數(shù) 前,檢查、處理、清理異常很重要。
  • 大部分 JNI函數(shù) 在隊(duì)列中有待處理異常時(shí),調(diào)用會(huì)導(dǎo)致一些無(wú)法預(yù)料的錯(cuò)誤,另一小部分 JNI函數(shù) 之所以能在這種情況下調(diào)用是因?yàn)檫@些函數(shù)為了處理異常而設(shè)計(jì)的。
  • 通常很有必要在發(fā)生異常的時(shí)候清理資源。
JNIEXPORT void JNICALL Java_pkg_Cls_f(JNIEnv * env, jclass cls, jstring jstr) {
    const jchar * cstr = (* env)->GetStringChars(env, jstr);
    if (c_str == NULL) {
        return;
    }

    // ...

    if (/* ... */) {
        // 發(fā)生異常,釋放資源
        (* env)->ReleaseStringChars(env, jstr, cstr);
        return;
    }

    // 函數(shù)結(jié)束時(shí),釋放資源
    (* env)->ReleaseStringChars(env, jstr, cstr);
}

效用函數(shù)里的異常

  • 對(duì)于 效用函數(shù) 里的異常,應(yīng)該要確保異常能夠傳播到 原生函數(shù) 調(diào)用者中。
  • 通常情況下, 效用函數(shù) 應(yīng)該提供一個(gè)特殊的返回值來(lái)表明異常在 效用函數(shù) 里發(fā)生了。
  • 應(yīng)該在 效用函數(shù) 的異常處理代碼中,處理好 局部引用

相關(guān)工具函數(shù)

用于便捷拋出異常

// name是異常的類型描述符,msg是拋出異常時(shí)需要輸出的信息
void JNU_ThrowByName(JNIEnv * env, const char * name, const char * msg) {
    // 此處若FindClass失敗,會(huì)拋出NoClassDefFoundError異常
    jclass cls = (* env)->FindClass(env, name);
    if (cls != NULL) {
        (* env)->ThrowNew(env, cls, msg);
    }
    // 若cls是NULL,DeleteLocalRef不會(huì)執(zhí)行任何操作
    (* env)->DeleteLocalRef(env, cls);
}

通用調(diào)用函數(shù)

代碼

// 參數(shù)說(shuō)明:
// hasException:用來(lái)接收異常是否拋出的信息,若非NULL,則代表著調(diào)用者關(guān)心是否有異常拋出
// obj:方法所屬的對(duì)象
// name:方法名字
// descriptor:方法描述符
jvalue
JNU_CallMethodByName(JNIEnv * env, jboolean * hasException, jobject obj, const char * name, const char * descriptor,
                     ...) {
    va_list args;
    // 聯(lián)合體
    jvalue result = NULL;
    if ((* env)->EnsureLocalCapacity(env, 2) == JNI_OK) {
        jclass clazz = (* env)->GetObjectClass(env, obj);
        jmethodID mid = (* env)->GetMethodID(env, clazz, name, descriptor);
        if (mid) {
            const char * p = descriptor;
            while (* p != ')') p++;
            p++;
            va_start(args, descriptor);
            switch (* p) {
                case 'V':
                    (* env)->CallVoidMethodV(env, obj, mid, args);
                    break;
                case '[':
                case 'L':
                    // result任意一個(gè)字段被初始化,都代表著有錯(cuò)誤發(fā)生
                    result.l = (* env)->CallObjectMethodV(env, obj, mid, args);
                    break;
                case 'Z':
                    result.z = (* env)->CallBooleanMethodV(env, obj, mid, args);
                    break;
                case 'B':
                    result.b = (* env)->CallByteMethodV(env, obj, mid, args);
                    break;
                case 'C':
                    result.c = (* env)->CallCharMethodV(env, obj, mid, args);
                    break;
                case 'S':
                    result.s = (* env)->CallShortMethodV(env, obj, mid, args);
                    break;
                case 'I':
                    result.i = (* env)->CallIntMethodV(env, obj, mid, args);
                    break;
                case 'J':
                    result.j = (* env)->CallLongMethodV(env, obj, mid, args);
                    break;
                case 'F':
                    result.f = (* env)->CallFloatMethodV(env, obj, mid, args);
                    break;
                case 'D':
                    result.d = (* env)->CallDoubleMethodV(env, obj, mid, args);
                    break;
                default:
                    (* env)->FatalError(env, "illegal descriptor");
            }
            va_end(args);
        }
        (* env)->DeleteLocalRef(env, clazz);
    }
    // 若調(diào)用者關(guān)心是否有異常拋出,則檢查是否有異常拋出
    if (hasException) {
        * hasException = (* env)->ExceptionCheck(env);
    }

//    // JDK release 1.1版本
//    if (hasException) {
//        jthrowable exc = (* env)->ExceptionOccurred(env);
//        * hasException = (jboolean) (exc != NULL);
//        (* env)->DeleteLocalRef(env, exc);
//    }

    return result;
}

使用例子

JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv * env, jobject obj) {
    printf("In C\n");
    JNU_CallMethodByName(env, NULL, obj, "callback", "()V");
    // 這里之所以不檢查錯(cuò)誤是因?yàn)闊o(wú)論是否發(fā)生錯(cuò)誤,都會(huì)直接return
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 應(yīng)用程序執(zhí)行時(shí),可能遇到各種可能的錯(cuò)誤。C#使用異常來(lái)處理這些錯(cuò)誤,異常將有關(guān)錯(cuò)誤的信息封裝在一個(gè)類中。異常設(shè)計(jì)用...
    CarlDonitz閱讀 970評(píng)論 0 0
  • ART世界探險(xiǎn)(10) - 異常處理 對(duì)于編譯Java的話,有一個(gè)問(wèn)題不能不考慮,就是異常處理的問(wèn)題。異常處理是基...
    Jtag特工閱讀 797評(píng)論 0 0
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,030評(píng)論 25 709
  • 前言 人生苦多,快來(lái) Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,692評(píng)論 9 118
  • 每天接觸過(guò)的客戶,業(yè)主和同事都盡可能加一下微信,回來(lái)把他們的名字寫(xiě)在本子上。這樣每月頭加10個(gè)人趁早星球打卡點(diǎn)贊,...
    6897e1c6fd79閱讀 588評(píng)論 1 2

友情鏈接更多精彩內(nèi)容