03.Android崩溃Crash库之ExceptionHandler分析

目录总结

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

前沿

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

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