Android捕獲崩潰異常

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

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

最終實現(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中進行配置

AndroidManifest.xml.png

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

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

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

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

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

完成以上過程后,接著需要重寫uncaughtException方法:

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

    // 打印異常信息
    ex.printStackTrace();
    // 我們沒有處理異常 并且默認異常處理不為空 則交給系統(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);
}

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

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

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

/**
 * 獲取異常信息
 * @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");

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

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

            //手機型號
            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卡這個我覺得可選吧,但是上傳到服務端還是很有必要的:

/**
 * 保存錯誤報告到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卡中的異常文件格式:

異常信息

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

因為捕獲異常后我要馬上關閉掉app即上面的byebye方法,是將app整個進程殺死,如果接著要顯示提示對話框,則需要在新的任務棧中打開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;
}

對話框中給出了重啟操作的選項,重啟過程的實現(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。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容