天天看点

Android崩溃处理及异常收集

目前为止也经历了好几个项目了,每个项目都会避免不了crash。这里就总结一下项目的异常处理。下面介绍一下系统提供的异常处理方法。

1.系统异常处理类。

/**
     * Interface for handlers invoked when a <tt>Thread</tt> abruptly
     * terminates due to an uncaught exception.
     * <p>When a thread is about to terminate due to an uncaught exception
     * the Java Virtual Machine will query the thread for its
     * <tt>UncaughtExceptionHandler</tt> using
     * {@link #getUncaughtExceptionHandler} and will invoke the handler's
     * <tt>uncaughtException</tt> method, passing the thread and the
     * exception as arguments.
     * If a thread has not had its <tt>UncaughtExceptionHandler</tt>
     * explicitly set, then its <tt>ThreadGroup</tt> object acts as its
     * <tt>UncaughtExceptionHandler</tt>. If the <tt>ThreadGroup</tt> object
     * has no
     * special requirements for dealing with the exception, it can forward
     * the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler
     * default uncaught exception handler}.
     *
     * @see #setDefaultUncaughtExceptionHandler
     * @see #setUncaughtExceptionHandler
     * @see ThreadGroup#uncaughtException
     * @since 1.5
     */
    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }
           

注释介绍的很清楚,这个接口是当线程因未捕获的异常而突然终止时调用的处理程序接口,当有未捕获异常的时候,系统就会调用实现了上述接口的异常处理程序来处理,然后系统默认实现最后会杀掉进程,这就是我们看到的应用程序的崩溃了。这里就不对默认的处理程序进行分析了。

一般来说我们的代码中如果使用了try catch来处理异常的话,就代表我们捕获了这个异常。这样的话异常就不会丢给系统默认的处理程序接口处理,也就不会崩溃。但是大部分情况下我们使用try catch仅仅是为了让应用不会崩溃,而没有做特殊处理,这样当应用程序发布之后是不利于上报bug的。

Android系统提供了如下方法来让我们处理未捕获的异常。

public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
         defaultUncaughtExceptionHandler = eh;
}
           

我们只需要实现前面介绍的接口,然后就可以通过上面的方法设置我们自己的用于处理未捕获异常的逻辑了。

2.防止应用崩溃

之前由于我们的应用使用的model中是对象里面嵌套了很多对象,这样的话一旦客户端使用的时候没有提前判空,然后服务端返回的数据中某个对象又为空,这样在运行的时候程序就会由于空指针而崩溃。然后在网上找了一下资料,发现了一种比较好的方式。大致的思路如下代码所示:

public class CrashDefender {
    public static void guard(){
        Handler t = new Handler(Looper.getMainLooper());
        t.post(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Looper.loop();
                    }catch (Throwable e){
                        Timber.tag("CrashDefender").e(e);
                        AnalyticsProxy.getInstance().logCrash(e);
                    }
                }
            }
        });

        Thread.setDefaultUncaughtExceptionHandler(new SimpleUncaughtExceptionHandler());
    }
}
           

我们知道Android的消息机制,在主线程也就是ActivityThread中会调用Looper.loop()开启主线程的消息循环。上面的主要思路是:

1.往主线程的消息队列中添加一个Runnable,然后在run方法里面做一个while死循环,并在循环中调用Looper.loop,这样主线程又开始读取消息,这样主线程就不会被阻塞。

2. 用于我们把这个Looper.loop()方法给try catch起来了,前面介绍过只要我们try catch了异常,应用就不会crash。这样主线程就不会crash。

3.Thread.setDefaultUncaughtExceptionHandler(new SimpleUncaughtExceptionHandler());而这句代码就会处理子线程的异常。

我们按如下方式定义了我们自己的程序处理

public class SimpleUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    private static final String TAG = "UncaughtException";

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        //读取stacktrace信息
        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        ex.printStackTrace(printWriter);
        String errorReport = result.toString();
        Timber.tag(TAG).e(ex);

        AnalyticsProxy.getInstance().logCrash(ex);
    }

}
           

 在应用的Application中调用如下代码,开启防护,经过这样的处理之后我们的应用就不会崩溃了。

private void initCrashDefender() {
        CrashDefender.guard();
}
           

3.崩溃信息上报

一个App应用一般都会接入数据统计的SDK,或者是自己公司的类似SDK。这些SDK中一般也是通过重新实现了上面提到的UncaughtExceptionHandler ,然后替换掉系统默认的实现。而初始化这些统计的SDK的时间是比较早的。然后我们上面防止应用崩溃也替换掉了系统的默认实现,同时也会覆盖掉这些第三方的处理。

但是搜集这些崩溃信息是非常重要的,于是我们在上面的防止崩溃的处理中,我们将主线程的崩溃异常,以及子线程的崩溃异常调用第三方SDK提供的接口,上报这些异常。这样我们就可以收集到这些信息了。

同时我们的一些业务返回的非200系列的code也可以包装成异常,然后抛出去。这样统计的sdk也能获取这一部分的信息,方便我们分析。简单的给一下代码。

public class ApiException extends Exception {
    private int code;
    private String message;

    public ApiException(Throwable throwable, String message, int code) {
        super(throwable);
        this.message = message;
        this.code = code;

    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public int getCode() {
        return code;
    }
}
           

这样我们就完成了整个异常的处理。

4.总结

上面介绍了一下项目中处理异常的方式,自己对andriod中的异常处理印象更加深刻了。能力就是这样不断总结中提高的,加油!

继续阅读