Android捕獲崩潰異常

由于我們寫(xiě)的代碼難免會(huì)出現(xiàn)一些bug,以及由于測(cè)試環(huán)境和生產(chǎn)環(huán)境差異導(dǎo)致出現(xiàn)的一些異常,在測(cè)試過(guò)程中沒(méi)有發(fā)現(xiàn),而在app上線之后會(huì)偶然出現(xiàn)的一些bug,以至于app在使用過(guò)程中出現(xiàn)ANR,這是個(gè)令人蛋疼的現(xiàn)象,app卡死、出現(xiàn)黑屏等,總之當(dāng)app出現(xiàn)異常時(shí)的用戶體驗(yàn)不友好,我們開(kāi)發(fā)者需要去捕獲這些異常,收集這些異常信息,并且上傳到服務(wù)器,利于開(kāi)發(fā)人員去解決這些問(wèn)題,同時(shí),我們還需要給用戶一個(gè)友好的交互體驗(yàn)。

我也查找了許多捕獲崩潰異常的文章來(lái)看,但大多都千篇一律,只說(shuō)了怎么捕獲異常,處理異常,并沒(méi)有對(duì)捕獲異常后的界面交互做出很好的處理,系統(tǒng)出現(xiàn)ANR時(shí)會(huì)先卡一段時(shí)間(這個(gè)時(shí)間還有點(diǎn)長(zhǎng))然后彈出一個(gè)系統(tǒng)默認(rèn)的對(duì)話框,但是我現(xiàn)在需要的是當(dāng)出現(xiàn)異常情況時(shí),立馬彈出對(duì)話框,并且對(duì)話框我想自定義界面。

最終實(shí)現(xiàn)的效果如圖:

<img src="http://upload-images.jianshu.io/upload_images/1159224-cdf0b4169183ecaf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" width="40%" alt="最終效果圖" align="center" />

首先需要自定義Application,并且在AndroidManifest.xml中進(jìn)行配置

AndroidManifest.xml.png

還需要自定義一個(gè)應(yīng)用異常捕獲類(lèi)AppUncaughtExceptionHandler,它必須得實(shí)現(xiàn)Thread.UncaughtExceptionHandler接口,另外還需要重寫(xiě)uncaughtException方法,去按我們自己的方式來(lái)處理異常

Application中我們只需要初始化自定義的異常捕獲類(lèi)即可:

@Override public void onCreate() {
    super.onCreate();
    mInstance = this;
    // 初始化文件目錄
    SdcardConfig.getInstance().initSdcard();
    // 捕捉異常
    AppUncaughtExceptionHandler crashHandler = AppUncaughtExceptionHandler.getInstance();
    crashHandler.init(getApplicationContext());
}

其中文件目錄是異常信息保存在sd卡中的目錄,還有我們是整個(gè)app中全局捕獲異常,所以我們自定義的捕獲類(lèi)是單例。

/**
 * 初始化捕獲類(lèi)
 *
 * @param context
 */
public void init(Context context) {
    applicationContext = context.getApplicationContext();
    crashing = false;
    //獲取系統(tǒng)默認(rèn)的UncaughtException處理器
    mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
    //設(shè)置該CrashHandler為程序的默認(rèn)處理器
    Thread.setDefaultUncaughtExceptionHandler(this);
}

完成以上過(guò)程后,接著需要重寫(xiě)uncaughtException方法:

@Override
public void uncaughtException(Thread thread, Throwable ex) {
    if (crashing) {
        return;
    }
    crashing = true;

    // 打印異常信息
    ex.printStackTrace();
    // 我們沒(méi)有處理異常 并且默認(rèn)異常處理不為空 則交給系統(tǒng)處理
    if (!handlelException(ex) && mDefaultHandler != null) {
        // 系統(tǒng)處理
        mDefaultHandler.uncaughtException(thread, ex);
    }
    byebye();
}

private void byebye() {
    android.os.Process.killProcess(android.os.Process.myPid());
    System.exit(0);
}

既然是我們自己處理異常,所以會(huì)先執(zhí)行handlelException(ex)方法:

private boolean handlelException(Throwable ex) {
    if (ex == null) {
        return false;
    }
    try {
        // 異常信息
        String crashReport = getCrashReport(ex);
        // TODO: 上傳日志到服務(wù)器
        // 保存到sd卡
        saveExceptionToSdcard(crashReport);
        // 提示對(duì)話框
        showPatchDialog();
    } catch (Exception e) {
        return false;
    }
    return true;
}

獲取的異常信息包括系統(tǒng)信息,app版本信息,以及手機(jī)制造商信息等:

/**
 * 獲取異常信息
 * @param ex
 * @return
 */
private String getCrashReport(Throwable ex) {
    StringBuffer exceptionStr = new StringBuffer();
    PackageInfo pinfo = CrashApplication.getInstance().getLocalPackageInfo();
    if (pinfo != null) {
        if (ex != null) {
            //app版本信息
            exceptionStr.append("App Version:" + pinfo.versionName);
            exceptionStr.append("_" + pinfo.versionCode + "\n");

            //手機(jī)系統(tǒng)信息
            exceptionStr.append("OS Version:" + Build.VERSION.RELEASE);
            exceptionStr.append("_");
            exceptionStr.append(Build.VERSION.SDK_INT + "\n");

            //手機(jī)制造商
            exceptionStr.append("Vendor: " + Build.MANUFACTURER+ "\n");

            //手機(jī)型號(hào)
            exceptionStr.append("Model: " + Build.MODEL+ "\n");

            String errorStr = ex.getLocalizedMessage();
            if (TextUtils.isEmpty(errorStr)) {
                errorStr = ex.getMessage();
            }
            if (TextUtils.isEmpty(errorStr)) {
                errorStr = ex.toString();
            }
            exceptionStr.append("Exception: " + errorStr + "\n");
            StackTraceElement[] elements = ex.getStackTrace();
            if (elements != null) {
                for (int i = 0; i < elements.length; i++) {
                    exceptionStr.append(elements[i].toString() + "\n");
                }
            }
        } else {
            exceptionStr.append("no exception. Throwable is null\n");
        }
        return exceptionStr.toString();
    } else {
        return "";
    }
}

將異常信息保存到sd卡這個(gè)我覺(jué)得可選吧,但是上傳到服務(wù)端還是很有必要的:

/**
 * 保存錯(cuò)誤報(bào)告到sd卡
 * @param errorReason
 */
private void saveExceptionToSdcard(String errorReason) {
    try {
        Log.e("CrashDemo", "AppUncaughtExceptionHandler執(zhí)行了一次");
        String time = mFormatter.format(new Date());
        String fileName = "Crash-" + time + ".log";
        if (SdcardConfig.getInstance().hasSDCard()) {
            String path = SdcardConfig.LOG_FOLDER;
            File dir = new File(path);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            FileOutputStream fos = new FileOutputStream(path + fileName);
            fos.write(errorReason.getBytes());
            fos.close();
        }
    } catch (Exception e) {
        Log.e("CrashDemo", "an error occured while writing file..." + e.getMessage());
    }
}

保存在sd卡中的異常文件格式:

異常信息

至于上傳到服務(wù)器,就比較靈活了,可以將整個(gè)文件上傳,或者上傳異常信息的字符串,可以和后端開(kāi)發(fā)人員配合。

因?yàn)椴东@異常后我要馬上關(guān)閉掉app即上面的byebye方法,是將app整個(gè)進(jìn)程殺死,如果接著要顯示提示對(duì)話框,則需要在新的任務(wù)棧中打開(kāi)activity

public static Intent newIntent(Context context, String title, String ultimateMessage) {

    Intent intent = new Intent();
    intent.setClass(context, PatchDialogActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    intent.putExtra(EXTRA_TITLE, title);
    intent.putExtra(EXTRA_ULTIMATE_MESSAGE, ultimateMessage);
    return intent;
}

對(duì)話框中給出了重啟操作的選項(xiàng),重啟過(guò)程的實(shí)現(xiàn):

private void restart() {
    Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    startActivity(intent);
    super.onDestroy();
}

源代碼地址:https://github.com/shenhuniurou/BlogDemos/tree/master/CrashDemo歡迎star。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容