阅读 365

03.Android崩溃Crash库之ExceptionHandler分析

目录总结

  • 00.异常处理几个常用api
  • 01.UncaughtExceptionHandler
  • 02.Java线程处理异常分析
  • 03.Android中线程处理异常分析
  • 04.为何使用setDefaultUncaughtExceptionHandler

前沿

  • 上一篇整体介绍了crash崩溃库崩溃重启,崩溃记录记录,查看以及分享日志等功能。
  • 项目地址:https://github.com/yangchong211/YCAndroidTool
  • 欢迎star

00.异常处理几个常用api

  • setUncaughtExceptionHandler
    • public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
      • 设置该线程由于未捕获到异常而突然终止时调用的处理程序。
      • 通过明确设置未捕获到的异常处理程序,线程可以完全控制它对未捕获到的异常作出响应的方式。
      • 如果没有设置这样的处理程序,则该线程的 ThreadGroup 对象将充当其处理程序。
    • public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()
      • 返回该线程由于未捕获到异常而突然终止时调用的处理程序。
      • 如果该线程尚未明确设置未捕获到的异常处理程序,则返回该线程的 ThreadGroup 对象,除非该线程已经终止,在这种情况下,将返回 null。
  • setDefaultUncaughtExceptionHandler
    • public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
      • 设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
      • 未捕获到的异常处理首先由线程控制,然后由线程的 ThreadGroup 对象控制,最后由未捕获到的默认异常处理程序控制。
      • 如果线程不设置明确的未捕获到的异常处理程序,并且该线程的线程组(包括父线程组)未特别指定其 uncaughtException 方法,则将调用默认处理程序的 uncaughtException 方法。
      -- 通过设置未捕获到的默认异常处理程序,应用程序可以为那些已经接受系统提供的任何“默认”行为的线程改变未捕获到的异常处理方式(如记录到某一特定设备或文件)。
    • public static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()
      • 返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
      • 如果返回值为 null,则没有默认处理程序。
  • Thread.UncaughtExceptionHandler
    • public static interface Thread.UncaughtExceptionHandler
      • 所有已知实现类:ThreadGroup
      • 当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。
      • 当某一线程因未捕获的异常而即将终止时,Java 虚拟机将使用 Thread.getUncaughtExceptionHandler() 查询该线程以获得其 UncaughtExceptionHandler 的线程,并调用处理程序的 uncaughtException 方法,将线程和异常作为参数传递。
      • 如果某一线程没有明确设置其 UncaughtExceptionHandler,则将它的 ThreadGroup 对象作为其 UncaughtExceptionHandler。
      • 如果 ThreadGroup 对象对处理异常没有什么特殊要求,那么它可以将调用转发给默认的未捕获异常处理程序。

01.UncaughtExceptionHandler

  • 官方介绍为:
    • Interface for handlers invoked when a Thread abruptly terminates due to an uncaught exception.
    • When a thread is about to terminate due to an uncaught exception the Java Virtual Machine will query the thread for its UncaughtExceptionHandler using getUncaughtExceptionHandler() and will invoke the handler's uncaughtException method, passing the thread and the exception as arguments. If a thread has not had its UncaughtExceptionHandler explicitly set, then its ThreadGroup object acts as its UncaughtExceptionHandler. If the ThreadGroup object has no special requirements for dealing with the exception, it can forward the invocation to the default uncaught exception handler.
  • 翻译后大概的意思是
    • UncaughtExceptionHandler接口用于处理因为一个未捕获的异常而导致一个线程突然终止问题。
    • 当一个线程因为一个未捕获的异常即将终止时,Java虚拟机将通过调用getUncaughtExceptionHandler() 函数去查询该线程的UncaughtExceptionHandler并调用处理器的 uncaughtException方法将线程及异常信息通过参数的形式传递进去。如果一个线程没有明确设置一个UncaughtExceptionHandler,那么ThreadGroup对象将会代替UncaughtExceptionHandler完成该行为。如果ThreadGroup没有明确指定处理该异常,ThreadGroup将转发给默认的处理未捕获的异常的处理器。
  • 异常回调:uncaughtException
    • uncaughtException (Thread t, Throwable e) 是一个抽象方法,当给定的线程因为发生了未捕获的异常而导致终止时将通过该方法将线程对象和异常对象传递进来。
  • 设置默认未捕获异常处理器:setDefaultUncaughtExceptionHandler
    • void setDefaultUncaughtExceptionHandler (Thread.UncaughtExceptionHandler eh)
    • 设置一个处理者当一个线程突然因为一个未捕获的异常而终止时将自动被调用。
    • 未捕获的异常处理的控制第一个被当前线程处理,如果该线程没有捕获并处理该异常,其将被线程的ThreadGroup对象处理,最后被默认的未捕获异常处理器处理。
    • 通过设置默认的未捕获异常的处理器,对于那些早已被系统提供了默认的未捕获异常处理器的线程,一个应用可以改变处理未捕获的异常的方式,例如记录到指定的设备或者文件。
  • handler将会报告线程终止和不明原因异常这个情况,如果没有自定义handler, 线程管理组就被默认为报告异常的handler。
    • ThreadHandler 这个类就是实现了UncaughtExceptionHandler这个接口,伪代码代码如下所示
    public class ThreadHandler implements Thread.UncaughtExceptionHandler {
    
        private Thread.UncaughtExceptionHandler mDefaultHandler;
        private boolean isInit = false;
        /**
         * CrashHandler实例
         */
        private static ThreadHandler INSTANCE;
    
        /**
         * 获取CrashHandler实例 ,单例模式
         */
        public static ThreadHandler getInstance() {
            if (INSTANCE == null) {
                synchronized (CrashHandler.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new ThreadHandler();
                    }
                }
            }
            return INSTANCE;
        }
    
        /**
         * 当UncaughtException发生时会转入该函数来处理
         * 该方法来实现对运行时线程进行异常处理
         */
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            if (mDefaultHandler != null) {
                //收集完信息后,交给系统自己处理崩溃
                //uncaughtException (Thread t, Throwable e) 是一个抽象方法
                //当给定的线程因为发生了未捕获的异常而导致终止时将通过该方法将线程对象和异常对象传递进来。
                mDefaultHandler.uncaughtException(t, e);
            } else {
                //否则自己处理
            }
        }
    
        /**
         * 初始化,注册Context对象,
         * 获取系统默认的UncaughtException处理器,
         * 设置该CrashHandler为程序的默认处理器
         * @param ctx
         */
        public void init(Application ctx) {
            if (isInit){
                return;
            }
            //获取系统默认的UncaughtExceptionHandler
            mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
            //将当前实例设为系统默认的异常处理器
            //设置一个处理者当一个线程突然因为一个未捕获的异常而终止时将自动被调用。
            //未捕获的异常处理的控制第一个被当前线程处理,如果该线程没有捕获并处理该异常,其将被线程的ThreadGroup对象处理,最后被默认的未捕获异常处理器处理。
            Thread.setDefaultUncaughtExceptionHandler(this);
            isInit = true;
        }
    }
    复制代码

02.Java线程处理异常分析

  • 线程出现未捕获异常后,JVM将调用Thread中的dispatchUncaughtException方法把异常传递给线程的未捕获异常处理器。
    public final void dispatchUncaughtException(Throwable e) {
        Thread.UncaughtExceptionHandler initialUeh = Thread.getUncaughtExceptionPreHandler();
        if (initialUeh != null) {
            try {
                initialUeh.uncaughtException(this, e);
            } catch (RuntimeException | Error ignored) {
                // Throwables thrown by the initial handler are ignored
            }
        }
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    
    public static UncaughtExceptionHandler getUncaughtExceptionPreHandler() {
        return uncaughtExceptionPreHandler;
    }
    复制代码
  • Thread中存在两个UncaughtExceptionHandler。一个是静态的defaultUncaughtExceptionHandler,另一个是非静态uncaughtExceptionHandler。
    // null unless explicitly set
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
    
    // null unless explicitly set
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
    复制代码
    • defaultUncaughtExceptionHandler:设置一个静态的默认的UncaughtExceptionHandler。来自所有线程中的Exception在抛出并且未捕获的情况下,都会从此路过。进程fork的时候设置的就是这个静态的defaultUncaughtExceptionHandler,管辖范围为整个进程。
    • uncaughtExceptionHandler:为单个线程设置一个属于线程自己的uncaughtExceptionHandler,辖范围比较小。
  • 没有设置uncaughtExceptionHandler怎么办?
    • 如果没有设置uncaughtExceptionHandler,将使用线程所在的线程组来处理这个未捕获异常。
    • 线程组ThreadGroup实现了UncaughtExceptionHandler,所以可以用来处理未捕获异常。ThreadGroup类定义:
    private ThreadGroup group;
    //可以发现ThreadGroup类是集成Thread.UncaughtExceptionHandler接口的
    class ThreadGroup implements Thread.UncaughtExceptionHandler{}
    复制代码
  • 然后看一下ThreadGroup中实现uncaughtException(Thread t, Throwable e)方法,代码如下
    • 默认情况下,线程组处理未捕获异常的逻辑是,首先将异常消息通知给父线程组,
    • 然后尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常,
    • 如果没有默认的异常处理器则将错误信息输出到System.err。
    • 也就是JVM提供给我们设置每个线程的具体的未捕获异常处理器,也提供了设置默认异常处理器的方法。
    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
    复制代码

03.Android中线程处理异常分析

  • 在Android平台中,应用进程fork出来后会为虚拟机设置一个未截获异常处理器, 即在程序运行时,如果有任何一个线程抛出了未被截获的异常, 那么该异常最终会抛给未截获异常处理器处理。
    • 具体可以找到RuntimeInit类,然后在找到KillApplicationHandler类。首先看该类的入口main方法--->commonInit()--->,然后接着往下走,找到setDefaultUncaughtExceptionHandler代码如下所示
    • 如果报告崩溃,不要再次进入——避免无限循环。如果ActivityThread分析器在此时运行,我们杀死进程,内存中的缓冲区将丢失。并且打开崩溃对话框
    • 最后会执行finally中杀死进程的方法干掉app
    Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
    
    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                if (mCrashing) return;
                mCrashing = true;
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }
                // Bring up crash dialog, wait for it to be dismissed
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                if (t2 instanceof DeadObjectException) {
                    // System process is dead; ignore
                } else {
                    try {
                        Clog_e(TAG, "Error reporting crash", t2);
                    } catch (Throwable t3) {
                        // Even Clog_e() fails!  Oh well.
                    }
                }
            } finally {
                // Try everything to make sure this process goes away.
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }
    }
    复制代码
  • UncaughtExceptionHandler存在于Thread中.当异常发生且未捕获时。异常会透过UncaughtExceptionHandler抛出。并且该线程会消亡。所以在Android中子线程死亡是允许的。主线程死亡就会导致ANR。
  • 所以其实在fork出app进程的时候,系统已经为app设置了一个异常处理,并且最终崩溃后会直接导致执行该handler的finallly方法最后杀死app直接退出app。如果你要自己处理,你可以自己实现Thread.UncaughtExceptionHandler。

04.为何使用setDefaultUncaughtExceptionHandler

  • Thread.UncaughtExceptionHandler 接口代码如下所示
    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        void uncaughtException(Thread t, Throwable e);
    }
    复制代码
    • UncaughtExceptionHandler 未捕获异常处理接口,当一个线程由于一个未捕获异常即将崩溃时,JVM 将会通过 getUncaughtExceptionHandler() 方法获取该线程的 UncaughtExceptionHandler,并将该线程和异常作为参数传给 uncaughtException()方法。
    • 如果没有显式设置线程的 UncaughtExceptionHandler,那么会将其 ThreadGroup 对象会作为 UncaughtExceptionHandler。
    • 如果其 ThreadGroup 对象没有特殊的处理异常的需求,那么就会调 getDefaultUncaughtExceptionHandler() 方法获取默认的 UncaughtExceptionHandler 来处理异常。
  • 难道要为每一个线程创建UncaughtExceptionHandler吗?
    • 应用程序通常都会创建很多线程,如果为每一个线程都设置一次 UncaughtExceptionHandler 未免太过麻烦。
    • 既然出现未处理异常后 JVM 最终都会调 getDefaultUncaughtExceptionHandler(),那么我们可以在应用启动时设置一个默认的未捕获异常处理器。即调用Thread.setDefaultUncaughtExceptionHandler(handler)
  • setDefaultUncaughtExceptionHandler被调用多次如何理解?
    • Thread.setDefaultUncaughtExceptionHandler(handler) 方法如果被多次调用的话,会以最后一次传递的 handler 为准,所以如果用了第三方的统计模块,可能会出现失灵的情况。对于这种情况,在设置默认 hander 之前,可以先通过 getDefaultUncaughtExceptionHandler() 方法获取并保留旧的 hander,然后在默认 handler 的uncaughtException 方法中调用其他 handler 的 uncaughtException 方法,保证都会收到异常信息。

项目地址:https://github.com/yangchong211/YCAndroidTool