转载

Java & Android未捕获异常处理机制

无论是Java还是Android项目,往往都会用到多线程。不管是主线程还是子线程,在运行过程中,都有可能出现未捕获异常。未捕获异常中含有详细的异常信息堆栈,可以很方便的去帮助我们排查问题。

默认情况下,异常信息堆栈都会在输出设备显示,同时,Java & Android为我们提供了未捕获异常的处理接口,使得我们可以去自定义异常的处理,甚至可以改变在异常处理流程上的具体走向,如常见的将异常信息写到本地日志文件,甚至上报服务端等。

在未捕获异常的处理机制上,总体上,Android基本沿用了Java的整套流程,同时,针对Android自身的特点,进行了一些特别的处理,使得在表现上与Java默认的流程会有一些差异。

二、未捕获异常处理流程

2.1 引子

我们先可以思考几个问题:

1,Java子线程中出现了未捕获的异常,是否会导致主进程退出?

2,Android子线程中出现了未捕获的异常,是否会导致App闪退?

3,Android项目中,当未作任何处理时,未捕获异常发生时,Logcat中的异常堆栈信息是如何输出的?

4,Android项目中,可能引入了多个质量监控的三方库,为何三方库之间,甚至与主工程之间都没有冲突?

5,Android中因未捕获异常导致闪退时,如何处理,从而可以将异常信息写到本地日志文件甚至上报服务端?

6,Java & Android对未捕获异常的处理流程有何异同?

先来看下第1个问题:

Java子线程中出现了未捕获的异常,是否会导致主进程退出?

可以做一个实验:

package com.corn.javalib;

public class MyClass {

    public static void main(String[] args) {
        System.out.println("thread name:" + Thread.currentThread().getName() + " begin...");

        Thread thread = new Thread(new MyRunnable());
        thread.start();

        try {
            Thread.currentThread().sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("thread name:" + Thread.currentThread().getName() + " end...");
    }

    static class MyRunnable implements Runnable {

        @Override
        public void run() {
            System.out.println("thread name:" + Thread.currentThread().getName() + " start run");

            errorMethod();

            System.out.println("thread name:" + Thread.currentThread().getName() + " end run");
        }
    }

    public static int errorMethod() {
        String name = null;

        return name.length();
    }

}
复制代码

执行Java程序,最后输出结果为:

thread name:main begin...
thread name:Thread-0 start run
Exception in thread "Thread-0" java.lang.NullPointerException
	at com.corn.javalib.MyClass.errorMethod(MyClass.java:35)
	at com.corn.javalib.MyClass$MyRunnable.run(MyClass.java:26)
	at java.lang.Thread.run(Thread.java:748)
thread name:main end...

Process finished with exit code 0
复制代码

我们发现,主线程中新起的子线程在运行时,出现了未捕获异常,但是,main主线程还是可以继续执行下去的,对整个进程而言,最终是 Process finished with exit code 0 ,说明也没有异常终止。

因此,第一个问题的结果是:

Java子线程中出现了未捕获的异常,默认情况下不会导致主进程异常终止。
复制代码

第2个问题:

Android子线程中出现了未捕获的异常,是否会导致App闪退?

同样的,新建Android工程后,模拟对应的场景,例如点击按钮,启动子线程,发现App直接闪退,AS Logcat中对应有如下日志输出:

2019-11-21 19:10:42.678 26259-26449/com.corn.crash I/System.out: thread name:Thread-2 start run
2019-11-21 19:10:42.679 26259-26449/com.corn.crash E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.corn.crash, PID: 26259
    java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
        at com.corn.crash.MainActivity.errorMethod(MainActivity.java:76)
        at com.corn.crash.MainActivity$MyRunnable.run(MainActivity.java:67)
        at java.lang.Thread.run(Thread.java:764)
2019-11-21 19:10:42.703 26259-26449/com.corn.crash I/Process: Sending signal. PID: 26259 SIG: 9
复制代码

从日志信息上看, SIG: 9 ,意味着App进程被kill掉,日志信息堆栈中给出了具体的异常位置,于是,我们得出如下结论:

默认情况下,Android子线程中出现了未捕获的异常,在是会导致App闪退的,且有异常信息堆栈输出。
复制代码

我们发现,基于Java基础上的Android,默认情况下,对于子线程中的未捕获异常,在进程是否异常退出方面,却有着相反的结果。

2.2 未捕获异常处理流程

接下来看下第3个问题:

Android项目中,当未作任何处理时,未捕获异常发生时,Logcat中的异常堆栈信息是如何输出的?
复制代码

当Android项目中出现未捕获异常时,Logcat中默认会自动有异常堆栈信息输出,且信息输出的前缀为: E/AndroidRuntime: FATAL EXCEPTION: 。我们很容易猜想到,这应该是系统层直接输出的,搜索framework源码,很快可以找到具体输出日志的位置:

Java & Android未捕获异常处理机制

RuntimeInit.java 中,找到了对应的异常日志输出位置,从代码注释上,我们找到了关键的 KillApplicationHandlerUncaughtExceptionHandler 类,先看下 KillApplicationHandler 类。

Java & Android未捕获异常处理机制
显然, KillApplicationHandler 是未捕获异常发生时,默认情况下最终杀死应用的最后处理类,通过调用其 uncaughtException 进行。 代码继续往下,可以找到设置 loggingHandlerKillApplicationHandler

的方法。

Java & Android未捕获异常处理机制

终于,我们可以得出第3个问题的答案:

默认情况下,未捕获异常发生时,Logcat中的异常堆栈信息,是从framework层,
具体是RuntimeInit.java类中的loggingHandler异常处理处理对象中的uncaughtException输出。
复制代码

loggingHandler 异常处理处理对象中的 uncaughtException 调用,具体又是在何处触发的呢?

从上述源码,以及对应的方法及代码注释中,我们大概已经知道了,未捕获异常的处理,与 UncaughtExceptionHandler 类有着莫大的关系。

UncaughtExceptionHandler ,实际上定义在 Thread 类中,并作为 interface 的形式存在,其内部,只有一个 uncaughtException 方法。

/**
 * 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);
}
复制代码

接口的注释中,基本上已经说明了未捕获异常的处理流程。我们将 Thread 类中关于未捕获异常的逻辑都截取出来,如下:

public class Thread implements Runnable {
    ....
    
    @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);
    }

    // null unless explicitly set
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

    // null unless explicitly set
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
    
    /**
     * Set the default handler invoked when a thread abruptly terminates
     * due to an uncaught exception, and no other handler has been defined
     * for that thread.
     *
     * <p>Uncaught exception handling is controlled first by the thread, then
     * by the thread's {@link ThreadGroup} object and finally by the default
     * uncaught exception handler. If the thread does not have an explicit
     * uncaught exception handler set, and the thread's thread group
     * (including parent thread groups)  does not specialize its
     * <tt>uncaughtException</tt> method, then the default handler's
     * <tt>uncaughtException</tt> method will be invoked.
     * <p>By setting the default uncaught exception handler, an application
     * can change the way in which uncaught exceptions are handled (such as
     * logging to a specific device, or file) for those threads that would
     * already accept whatever "default" behavior the system
     * provided.
     *
     * <p>Note that the default uncaught exception handler should not usually
     * defer to the thread's <tt>ThreadGroup</tt> object, as that could cause
     * infinite recursion.
     *
     * @param eh the object to use as the default uncaught exception handler.
     * If <tt>null</tt> then there is no default handler.
     *
     * @throws SecurityException if a security manager is present and it
     *         denies <tt>{@link RuntimePermission}
     *         ("setDefaultUncaughtExceptionHandler")</tt>
     *
     * @see #setUncaughtExceptionHandler
     * @see #getUncaughtExceptionHandler
     * @see ThreadGroup#uncaughtException
     * @since 1.5
     */
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
         defaultUncaughtExceptionHandler = eh;
     }

    /**
     * Returns the default handler invoked when a thread abruptly terminates
     * due to an uncaught exception. If the returned value is <tt>null</tt>,
     * there is no default.
     * @since 1.5
     * @see #setDefaultUncaughtExceptionHandler
     * @return the default uncaught exception handler for all threads
     */
    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
        return defaultUncaughtExceptionHandler;
    }

    // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
    // See http://b/29624607 for background information.
    // null unless explicitly set
    private static volatile UncaughtExceptionHandler uncaughtExceptionPreHandler;

    /**
     * Sets an {@link UncaughtExceptionHandler} that will be called before any
     * returned by {@link #getUncaughtExceptionHandler()}. To allow the standard
     * handlers to run, this handler should never terminate this process. Any
     * throwables thrown by the handler will be ignored by
     * {@link #dispatchUncaughtException(Throwable)}.
     *
     * @hide used when configuring the runtime for exception logging; see
     *     {@link dalvik.system.RuntimeHooks} b/29624607
     */
    public static void setUncaughtExceptionPreHandler(UncaughtExceptionHandler eh) {
        uncaughtExceptionPreHandler = eh;
    }

    /** @hide */
    public static UncaughtExceptionHandler getUncaughtExceptionPreHandler() {
        return uncaughtExceptionPreHandler;
    }
    // END Android-added: uncaughtExceptionPreHandler for use by platform.

    /**
     * Returns the handler invoked when this thread abruptly terminates
     * due to an uncaught exception. If this thread has not had an
     * uncaught exception handler explicitly set then this thread's
     * <tt>ThreadGroup</tt> object is returned, unless this thread
     * has terminated, in which case <tt>null</tt> is returned.
     * @since 1.5
     * @return the uncaught exception handler for this thread
     */
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

    /**
     * Set the handler invoked when this thread abruptly terminates
     * due to an uncaught exception.
     * <p>A thread can take full control of how it responds to uncaught
     * exceptions by having its uncaught exception handler explicitly set.
     * If no such handler is set then the thread's <tt>ThreadGroup</tt>
     * object acts as its handler.
     * @param eh the object to use as this thread's uncaught exception
     * handler. If <tt>null</tt> then this thread has no explicit handler.
     * @throws  SecurityException  if the current thread is not allowed to
     *          modify this thread.
     * @see #setDefaultUncaughtExceptionHandler
     * @see ThreadGroup#uncaughtException
     * @since 1.5
     */
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the runtime and by tests.
     *
     * @hide
     */
    // Android-changed: Make dispatchUncaughtException() public, for use by tests.
    public final void dispatchUncaughtException(Throwable e) {
        // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
        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
            }
        }
        // END Android-added: uncaughtExceptionPreHandler for use by platform.
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
    
    ....
}

复制代码

从源码及注释整个分析下来,对于未捕获异常,得出如下处理流程:

1,运行时发生异常时,系统会调用 dispatchUncaughtException ,开始执行异常的分发处理流程;

2, dispatchUncaughtException 中,先判断有无 异常预处理器 ,即 uncaughtExceptionPreHandler ,有的话,将会先调用 异常预处理器uncaughtException 方法;

3,接下来获取 异常处理器 ,并调用其 uncaughtException 方法。至此,整个异常分发处理流程完毕。

异常预处理器 在前述 RuntimeInit.java 类的 loggingHandler 中,我们已经有所接触,在App进程启动时,系统会自动注入 loggingHandler 对象,作为 异常预处理器 。当有未捕获异常发生时,以此会自动调用 loggingHandler 对象的 uncaughtException 方法,以完成默认的日志输出。

至此,第3个问题的完整回答是:

未捕获异常发生时,系统会调用Thread类的dispatchUncaughtException方法,
方法中取到异常预处理器,并执行对应uncaughtException方法。
由于App进程启动时,系统已经在RuntimeInit.java类中注册了一个默认的异常预处理器loggingHandler。
因此,loggingHandler得以回调,并执行了其uncaughtException方法,输出了异常的堆栈信息。
复制代码

当然,系统为我们提供了 异常预处理器 的设置接口,如果我们通过 setUncaughtExceptionPreHandler(ncaughtExceptionHandler eh) 方法设置了 异常预处理器 ,那默认的 loggingHandler 将会失效。因为静态变量 uncaughtExceptionPreHandler 被重新赋值了嘛,但此方法被设置成了 @hide ,当前可以通过反射去设置。

这里,我们也应该认识到,正因为 uncaughtExceptionPreHandler 为静态变量,因此,同一进程中的所有线程的 异常预处理器 都是相同的。

下面,我们开始着重看下 异常处理器 的异常处理流程。对应代码为:

getUncaughtExceptionHandler().uncaughtException(this, e);
复制代码

getUncaughtExceptionHandler() ,返回的一个异常处理器,具体对应方法定义如下:

/**
 * Returns the handler invoked when this thread abruptly terminates
 * due to an uncaught exception. If this thread has not had an
 * uncaught exception handler explicitly set then this thread's
 * <tt>ThreadGroup</tt> object is returned, unless this thread
 * has terminated, in which case <tt>null</tt> is returned.
 * @since 1.5
 * @return the uncaught exception handler for this thread
 */
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ?
        uncaughtExceptionHandler : group;
}
复制代码

首先判断 uncaughtExceptionHandler 变量是否赋值,如果有值将直接返回此异常处理器,否则返回的是 groupuncaughtExceptionHandler 是一个对象类型的属性变量,并非 static 的静态变量,这也意味着,每个线程,都可以通过 setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 方法设置线程私有的 异常处理器 ,并且,一旦设置,如果有未捕获异常,此 异常处理器 将被调用,异常处理流程结束。

group 具体类型是 ThreadGroup ,并实现了 Thread.UncaughtExceptionHandler 接口。 ThreadGroup 中关于未捕获异常处理的逻辑截取如下:

public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    ....
    
    /**
     * Called by the Java Virtual Machine when a thread in this
     * thread group stops because of an uncaught exception, and the thread
     * does not have a specific {@link Thread.UncaughtExceptionHandler}
     * installed.
     * <p>
     * The <code>uncaughtException</code> method of
     * <code>ThreadGroup</code> does the following:
     * <ul>
     * <li>If this thread group has a parent thread group, the
     *     <code>uncaughtException</code> method of that parent is called
     *     with the same two arguments.
     * <li>Otherwise, this method checks to see if there is a
     *     {@linkplain Thread#getDefaultUncaughtExceptionHandler default
     *     uncaught exception handler} installed, and if so, its
     *     <code>uncaughtException</code> method is called with the same
     *     two arguments.
     * <li>Otherwise, this method determines if the <code>Throwable</code>
     *     argument is an instance of {@link ThreadDeath}. If so, nothing
     *     special is done. Otherwise, a message containing the
     *     thread's name, as returned from the thread's {@link
     *     Thread#getName getName} method, and a stack backtrace,
     *     using the <code>Throwable</code>'s {@link
     *     Throwable#printStackTrace printStackTrace} method, is
     *     printed to the {@linkplain System#err standard error stream}.
     * </ul>
     * <p>
     * Applications can override this method in subclasses of
     * <code>ThreadGroup</code> to provide alternative handling of
     * uncaught exceptions.
     *
     * @param   t   the thread that is about to exit.
     * @param   e   the uncaught exception.
     * @since   JDK1.0
     */
    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);
            }
        }
    }
    
    ....
}
复制代码

当线程私有的 uncaughtExceptionHandler 变量为空时,此时调用到。 ThreadGroupuncaughtException 方法。这个方法内部逻辑稍显复杂,具体流程如下:

1,先判断是否有父线程组,只要存在父线程组,都将会先调用父线程组的 uncaughtException 方法;

2,直到父线程组为null时,此时已经是根线程组了,将会通过 Thread.getDefaultUncaughtExceptionHandler() 获取 线程默认的异常处理器

3,如果 线程默认的异常处理器 存在,将直接调用 线程默认异常处理器uncaughtException 方法,流程结束;

4,否则,将会通过 e.printStackTrace ,输出异常信息。

同样的,我们需要注意的是, 线程默认的异常处理器 也是一个 static 定义在 Thread 类中的静态变量,跟 异常预处理器 一样,也就意味着这是所有线程共享的。在前述的 RuntimeInit.java 类中 KillApplicationHandler 类的对象,就是通过 setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) 设置进去的。也就是说,App启动时,系统会默认为其设置一个 线程默认的异常处理器 ,当未捕获异常发生时,默认情况下的闪退就是这个 线程默认的异常处理器 ,即 KillApplicationHandler 去具体触发的。

当然了,我们也可以人为的设置 线程线程默认的异常处理器 ,此时,如果流程执行到这,将会按照我们设置的异常处理器去处理。

总体上,我们可以画一个流程图,总结下上述的整个流程。

Java &amp; Android未捕获异常处理机制

通过设置 异常预处理器线程默认的异常处理器 或者 线程私有的异常处理器 ,都可以实现对未捕获异常的自定义异常的处理,或者改变其默认的执行流程。更有甚者,我们可以将线程归组,同时自定义线程组,并重写其 uncaughtException 方法,以实现对特定线程组的异常处理的自定义。凡此种种,处理起来可以依据实际需要,非常灵活。

很自然的,我们可以很容易地回答第4个问题:

Android项目中,可能引入了多个质量监控的三方库,为何三方库之间,甚至与主工程之间都没有冲突?
复制代码

例如项目中接入了腾讯的bugly,同时又接入了友盟或firebase,且项目自身,往往还自定义了异常处理器。这在实际项目开发中是非常常见的。当有未捕获异常出现时,多个质量监控的后台,都能有效收集到对应的错误信息。这也是实际上都知道的“常识”。之所以彼此之间没有互相冲突,也没有相互影响,原因在于大家都是遵循同样的一套原则去处理未捕获的异常,而未实际去阻断或不可逆的直接改变未捕获异常的流程。例如:各自自定义异常处理时,先获取 线程默认的异常处理器 ,暂存起来,然后各自设置自定义的异常处理器,但在实现的 uncaughtException 方法中,处理完自己的逻辑后,适时的去调用原有的 线程默认的异常处理 。如此,表面上看,是 static 静态变量(线程默认的异常处理器)每次被重新覆盖,实际上却达到了彼此间的自定义的异常处理逻辑都能实现,互不影响。

如:

public class CrashReport implements UncaughtExceptionHandler {
    private final static String TAG = "CrashReport";

    private final static CrashReport INSTANCE = new CrashReport();
    private Thread.UncaughtExceptionHandler mDefaultHandler;

    private CrashReport() {

    }

    public static CrashReport getInstance() {
        return INSTANCE;
    }

    /**
     * 初始化,注册Context对象,
     * 获取系统默认的UncaughtException处理器,
     * 设置该CrashHandler为程序的默认处理器
     */
    public void init() {
        if (Thread.getDefaultUncaughtExceptionHandler() != this) {
            mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
            Thread.setDefaultUncaughtExceptionHandler(this);
        }
    }

    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        // 实现自定义的未捕获异常处理逻辑,例如上报自己的服务器等。
        .....
        .....
        
        // 调用原有的线程默认的异常处理器处理异常
        if (mDefaultHandler != null && mDefaultHandler != this) {
            mDefaultHandler.uncaughtException(thread, ex);
        }
    }
}
复制代码

自然的,实际上,第5个问题也已经回答完了。

2.3 Java & Android 未捕获异常处理流程的异同

接下来开始回答第6个问题。

从上述分析的流程及源码中可以看出,未捕获异常的处理流程上,最核心的涉及到的是 java.lang.Threadjava.lang.ThreadGroup 以及 com.android.internal.os.RuntimeInit 类。但是 RuntimeInit 是Android中特有的类,这也就意味着,单纯的Java环境下,是没有默认被系统注入的 uncaughtExceptionPreHandlerdefaultUncaughtExceptionHandler 异常处理器的。

同时,在源码中,发现针对 setUncaughtExceptionPreHandler 方法有如下注释部分:

/**
 * Sets an {@link UncaughtExceptionHandler} that will be called before any
 * returned by {@link #getUncaughtExceptionHandler()}. To allow the standard
 * handlers to run, this handler should never terminate this process. Any
 * throwables thrown by the handler will be ignored by
 * {@link #dispatchUncaughtException(Throwable)}.
 *
 * @hide only for use by the Android framework (RuntimeInit) b/29624607
 */
public static void setUncaughtExceptionPreHandler(UncaughtExceptionHandler eh) {
    uncaughtExceptionPreHandler = eh;
}

/** @hide */
public static UncaughtExceptionHandler getUncaughtExceptionPreHandler() {
    return uncaughtExceptionPreHandler;
}
复制代码

显然,从注释中可以看出, uncaughtExceptionPreHandler 只是Android中才特有的概念,Java中是没有的。

因为Android中用到的,是基于OpendJDK版本的Java,并非Oracle的Java版本。在OpendJDK版本的Java中,针对Android系统特有的需求,增加了 线程预处理器 的概念,并让其在其他异常处理器之前执行。

再次用流程图表示下,其中浅红色区域,是Java & Android 未捕获异常处理流程的差异部分。

Java &amp; Android未捕获异常处理机制

三、结语

Java & Android 未捕获异常处理流程总体上是类似的,除了Android特有的线程异常预处理器和默认设置的 uncaughtExceptionPreHandlerdefaultUncaughtExceptionHandler 。Android项目开发中,可以依据实际的情况,去增加特有的异常处理逻辑,甚至去改变异常处理的流程走向。只要你愿意,甚至当未捕获异常发生时,App不闪退都是完全可以的。

Just do it

end ~

原文  https://juejin.im/post/5dd52e156fb9a05a7523778e
正文到此结束
Loading...