App的异常崩溃处理

做任何软件,都需要考虑异常情况的处理,这是软件的可维护性的一部分。
异常崩溃是一种罕见的极端异常情况,这种情况下,针对终端用户的UI反馈、事故设备的信息采集、向后台维护人员的数据反馈等,都需要精心的设计。

UI反馈

  • 要做反馈,首先要抓到所有的异常崩溃。
    异常崩溃都是App进程的异常,每个App进程都运行在该App的Application中,所以我们可以在Application上集中抓到所有的进程异常:
    private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
    private Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() {
        if (uncaughtExceptionHandler == null) {
            uncaughtExceptionHandler = CrashHandler.getInstance(this,this);
        }
        return uncaughtExceptionHandler;
    }
    private void init(){
        Thread.setDefaultUncaughtExceptionHandler(getUncaughtExceptionHandler());
    }

我们抓到所有的进程异常,然后统一抛给一个CrashHandler类去处理,这个CrashHandler要实现UncaughtExceptionHandler接口:

public class CrashHandler implements UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
    }
}
  • 尽力保持用户数据的完整性,并设法恢复崩溃前的界面。
    如果要在崩溃时重启App,就需要在退出App前,再次StartActivity
            Intent i = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName());
            i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            mContext.startActivity(i);
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(0);

其中,android.os.Process.killProcess(Dalvik虚拟机方法)和System.exit(0)(常规java方法)起到的作用一样。
这里有一个关于Activity栈的陷阱,上面的代码可以重启入口Activity,但是,如果这时你这个App的Activity栈里还有其他的Activity,这些Activity是仍然存在的,不会被销毁。
推荐的做法是在Application中维护一个Activity的列表,专门管理所有的Activity,在必要时,通过这个列表,去销毁所有的Activity

    private ActivityStack stack;//扩展LinkList自定义一个activity列表
    //用set注入
    @Override
    public void initActivityStack(ActivityStack stack) {
        this.stack=stack;
    }
    //activity在oncreate时做添加//
    @Override
    public void addActivity(Activity activity) {
        if(stack!=null)stack.addStack(activity);
    }
    //activity在ondestroy时做删减//
    @Override
    public void removeActivity(Activity activity) {
        if(stack!=null)stack.remove(activity);
    }
    @Override
    public void clearAll() {
        if(stack!=null)stack.clearAll();
    }

在这个自定义的activity列表里,通过调用Activity的finish,来销毁Activity

public void clearAll() {
        if(stack!=null&&stack.size()>0) {
            for (Activity activity : stack) {
                if (activity != null) {
                    activity.finish();
                }
            }
            stack.clear();
        }
    }

注意,为了避免Application持有Activity导致内存泄露,在Activity的生命周期里不能只写入列,还要记得写出列。

  • 然后要提示用户发生了一些事情,这里要谨慎措辞,最好根据产品特性设计一些符合产品气质的提示语,这里就不展开了。
  • 最后,在自动重启和退出App之间寻找平衡,比如第一次崩溃当然可以自动重启,如果遇到特殊因素导致连续崩溃(如:接口问题或运行环境问题),就需要人为限制重启的次数或频率(比如,记录上次自动重启的时间,判断两次重启的时间间隔是否过窄),避免成为用户眼中的流氓软件。

信息采集

对异常崩溃了解的越多,就越容易处理它,所以我们要尽可能地采集相关信息。
对研发来说,最有用的当然是异常代码行(如MainActivity第61行)和异常原因(如空指针异常),在研发环境里,我们可以通过logcat读到这些信息,那没什么可说的,我们要考虑的是,如果异常崩溃发生在万里之外的生产环境,我们要怎样采集信息。

  • 首先,要创建和保存本地log文件夹,专门保存这些信息,一方面,网络不是一直可靠的,保存到本地可以避免数据丢失;另一方面,无论是远程数据上传还是现场同僚手动拷贝,都需要有这样一个文件夹。
  • 然后,要抓取异常代码行和异常原因,也就是你在logcat里读到的那些异常堆栈信息,所有的Java异常都会抛出一个Throwable,这里面就能找到这些异常堆栈信息(需要用printwriter去读)
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();

        String result = writer.toString();
  • 最后,有些崩溃是在特定的软硬件环境下出现的,我们需要知道这些环境信息:
        Map<String, String> infos = new HashMap<String, String>();
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
            } catch (Exception e) {
                Log.e(TAG, "an error occured when collect crash info", e);
            }
        }
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }

数据反馈

首选当然是通过后台网络默默反馈(在WIFI环境下,避免消耗用户流量)。
如果是以文件为单位上传反馈,只要做好锁文件和销毁文件即可。
如果是在线实时上传反馈,就需要为每次崩溃编号,或根据编号依次上传,或在后台进行合并过滤。
需要注意的是,本地日志文件不能过大,如果超过一定大小限制,要有自动清理机制,比如删除日期最早的那个文件,是的,强烈建议根据日期来建立多个日志文件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容