【Android】为啥子线程抛出异常主线程会崩溃?UncaughtExceptionHandler

学而不思则罔,思而不学则殆


引言

UncaughtExceptionHandler是Thread内部的一个接口:

public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the given uncaught exception.
         * 方法在给定线程由于给定未捕获异常而终止时调用。
         * Any exception thrown by this method will be ignored by the Java Virtual Machine.
         * 此方法引发的任何异常都将被Java虚拟机忽略。 
         */
        void uncaughtException(Thread t, Throwable e);
    }

UncaughtExceptionHandler

Caught - 捕获 Uncaught - 未捕获
Exception - 异常
Handler - 处理者

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.
在线程由于未捕获异常而突然终止时调用的处理程序的接口。
调用时通过查询getUncaughtExceptionHandler 
如果一个线程还没有显式设置它的UncaughtExceptionHandler,那么它的ThreadGroup对象就充当它的UncaughtExceptionHandler。
如果ThreadGroup对象没有处理异常的特殊要求,它可以将调用转发给默认的未捕获异常处理程序。

后面两句主要是描述了使用UncaughtExceptionHandler的顺序逻辑。
一般有三种UncaughtExceptionHandler:

UncaughtExceptionHandler种类优先级
线程私有的UncaughtExceptionHandler最高
ThreadGroup的UncaughtExceptionHandler其次
静态默认的UncaughtExceptionHandler最后

当然如果最后都没有UncaughtExceptionHandler处理这种未捕获的异常,就默认处理,打印错误堆栈异常。

测试

测试uncaughtException的调用链

设置当前线程的未捕获异常处理器,通过setUncaughtExceptionHandler。然后再线程中抛出异常,测试结果.

    private static void testSetUncaughtExceptionHandler() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int y = new Random().nextInt(2); //这里有可能 y = 0
                System.out.println(y);
                int x = 10 / y;  //抛出异常
                System.out.println("testChild end");
            }
        });
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("uncaughtException:" + Thread.currentThread() + " " + t + " " + e);
            }
        });
        thread.start();

        try {
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main end " + Thread.currentThread());
    }

以下代码是我的测试结果:
在这里插入图片描述

Java getDefaultUncaughtExceptionHandler:null
0
uncaughtException:Thread[Thread-0,5,main] Thread[Thread-0,5,main] java.lang.ArithmeticException: / by zero
Main end Thread[main,5,main]

可以看到,当捕获到未被处理的异常,系统把线程和异常作为参数回调了uncaughtException方法。根据断点的路劲我们知道该回调的调用起点是dispatchUncaughtException:

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

官方解释:

向处理程序发送未捕获的异常,该方法只被JVM调用

当我们不设置UncaughtExceptionHandler的时候,结果如下:

Java getDefaultUncaughtExceptionHandler:null
0
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
	at com.thread.UncaughtExceptionHandlerTest$1.run(UncaughtExceptionHandlerTest.java:22)
	at java.lang.Thread.run(Thread.java:748)
Main end Thread[main,5,main]

结论结果我们知道两点结论:

  1. 默认没有UncaughtExceptionHandler的时候,系统会打印出崩溃异常路径
  2. 子线程崩溃,main线程是不受影响,继续执行,直到休眠结束。

那么这里就到了本篇文章需要讨论的点,为啥Android是子线程崩溃后,整个进程都跟着崩溃了呢?
接下来进入分析的重点,Android和Java的默认UncaughtExceptionHandler

获取默认UncaughtExceptionHandler

测试Java

    public static void main(String[] args) {
        Thread.UncaughtExceptionHandler uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        System.out.println("Java getDefaultUncaughtExceptionHandler:" + uncaughtExceptionHandler);
    }

以上代码测试结果:

Java getDefaultUncaughtExceptionHandler:null

测试Android

    public void getDefaultUncaughtExceptionHandler(View view) {
        Thread.UncaughtExceptionHandler uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Log.d("zhangyu20201220", "Android getDefaultUncaughtExceptionHandler:" + uncaughtExceptionHandler);
    }

以上代码测试结果:

Android getDefaultUncaughtExceptionHandler:com.android.internal.os.RuntimeInit$KillApplicationHandler@45c7407

通过上面的测试我们得出结论:

Java没有默认UncaughtExceptionHandler ,Android有默认的UncaughtExceptionHandler (RuntimeInit$KillApplicationHandler)

接下来就要分析Andorid的默认UncaughtExceptionHandler 。

源码分析+找源码

我们先贴出源码:

public class RuntimeInit {
    ...
    /**
     * Logs a message when a thread encounters an uncaught exception. By
     * default, {@link KillApplicationHandler} will terminate this process later,
     * but apps can override that behavior.
     */
    private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
        public volatile boolean mTriggered = false;

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            //...打印log信息...
            }
        }
    }

    /**
     * Handle application death from an uncaught exception.  The framework
     * catches these for the main threads, so this should only matter for
     * threads created by applications. Before this method runs, the given
     * instance of {@link LoggingHandler} should already have logged details
     * (and if not it is run first).
     */
    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;

        /**
         * Create a new KillApplicationHandler that follows the given LoggingHandler.
         * If {@link #uncaughtException(Thread, Throwable) uncaughtException} is called
         * on the created instance without {@code loggingHandler} having been triggered,
         * {@link LoggingHandler#uncaughtException(Thread, Throwable)
         * loggingHandler.uncaughtException} will be called first.
         *
         * @param loggingHandler the {@link LoggingHandler} expected to have run before
         *     this instance's {@link #uncaughtException(Thread, Throwable) uncaughtException}
         *     is being called.
         */
        public KillApplicationHandler(LoggingHandler loggingHandler) {
            this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                ensureLogging(t, e); //打印log
                ...
            } catch (Throwable t2) {
                ...
            } finally {
                // Try everything to make sure this process goes away.
                Process.killProcess(Process.myPid());  // 杀死当前进程
                System.exit(10);
            }
        }
        ......
    }
}

删除中间的一些干扰代码,只保留本篇文章关注的重点,进程。我们可以看到在KillApplicationHandler 是的接口中,uncaughtException方法在finally的地方,Kill掉了整个进程。这也就是为啥子线程崩溃整个进程也崩溃的原因。

他们的类图如下:
在这里插入图片描述

疑问三连

那么Android为什么要这个做呢?

思考不是很全,我的理解Android考虑到安全性,所以这么设计的。

或者那位读者大佬有其他的理解,欢迎评论指出,大家一起学习。

那么可以做到Android是子线程崩溃后,整个进程不崩溃吗?

可以的,线程可以设置的私有UncaughtExceptionHandler,也可以设置ThreadGroup ,也可以设置DefaultUncaughtExceptionHandler来实现自己的逻辑

该机制的应用有哪些?

目前我知道的有,项目中通过这个方法获取异常,收起异常崩溃信息,进行上传和分析。比如常用的CrashHandler,Bugly等崩溃日志记录框架

Demo

https://github.com/aJanefish/ViewTest

总结

总结一下整篇文章的知识点:

  1. UncaughtExceptionHandler的获取顺序(当前线程-》ThreadGroup -》DefaultUncaughtExceptionHandler)
  2. 默认没有UncaughtExceptionHandler的时候,系统会打印出崩溃异常路径
  3. Java中子线程崩溃,main线程是不受影响,继续执行,直到休眠结束。
  4. Android是子线程崩溃后,整个进程都崩溃
  5. Java没有默认UncaughtExceptionHandler ,Android有默认的UncaughtExceptionHandler(RuntimeInit$KillApplicationHandler)
  6. Android是子线程崩溃后,整个进程都崩溃,崩溃原因是:KillApplicationHandler中kill掉了整个进程
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值