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

附录;

附录一;Android高级技术大纲

附录二;Android进阶实战技术视频

扫描二维码关注公众号,回复: 4970083 查看本文章

获取方式;

加Android进阶群;701740775。即可前往免费领取。免费备注一下csdn

猜你喜欢

转载自blog.csdn.net/feiyu1947/article/details/86539176
今日推荐