Android捕獲崩潰異常

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

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

最終實(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)用異常捕獲類AppUncaughtExceptionHandler肖爵,它必須得實(shí)現(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卡中的目錄劝堪,還有我們是整個(gè)app中全局捕獲異常,所以我們自定義的捕獲類是單例揉稚。

/**
 * 初始化捕獲類
 *
 * @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);
}

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

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

    // 打印異常信息
    ex.printStackTrace();
    // 我們沒有處理異常 并且默認(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);
        // 提示對話框
        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ī)型號
            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è)我覺得可選吧,但是上傳到服務(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è)文件上傳,或者上傳異常信息的字符串猜拾,可以和后端開發(fā)人員配合即舌。

因?yàn)椴东@異常后我要馬上關(guān)閉掉app即上面的byebye方法,是將app整個(gè)進(jìn)程殺死挎袜,如果接著要顯示提示對話框顽聂,則需要在新的任務(wù)棧中打開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àng),重啟過程的實(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)容合作請聯(lián)系作者
  • 序言:七十年代末紊搪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子全景,更是在濱河造成了極大的恐慌嗦明,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚪燕,死亡現(xiàn)場離奇詭異娶牌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)馆纳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門诗良,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鲁驶,你說我怎么就攤上這事鉴裹。” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵径荔,是天一觀的道長督禽。 經(jīng)常有香客問我,道長总处,這世上最難降的妖魔是什么狈惫? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮鹦马,結(jié)果婚禮上胧谈,老公的妹妹穿的比我還像新娘。我一直安慰自己荸频,他們只是感情好菱肖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旭从,像睡著了一般稳强。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上和悦,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天退疫,我揣著相機(jī)與錄音,去河邊找鬼摹闽。 笑死蹄咖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的付鹿。 我是一名探鬼主播澜汤,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舵匾!你這毒婦竟也來了俊抵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坐梯,失蹤者是張志新(化名)和其女友劉穎徽诲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吵血,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谎替,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蹋辅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钱贯。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖侦另,靈堂內(nèi)的尸體忽然破棺而出秩命,到底是詐尸還是另有隱情尉共,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布弃锐,位于F島的核電站袄友,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏霹菊。R本人自食惡果不足惜剧蚣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浇辜。 院中可真熱鬧券敌,春花似錦唾戚、人聲如沸柳洋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熊镣。三九已至,卻和暖如春募书,著一層夾襖步出監(jiān)牢的瞬間绪囱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工莹捡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鬼吵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓篮赢,卻偏偏與公主長得像齿椅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子启泣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

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