【Android】【源码分析】应用内 Crash 异常日志收集(UncaughtExceptionHandler)

       应用内部收集 Crash 日志主要是基于 Thread.java 的 UncaughtExceptionHandler。据了解,
第三方异常日志统计主要也是基于该模块。


1. 基本原理

/**
* 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);
}

                                                                       (代码 1,来自 Thread.java)


       注释部分可以知道:这个方法是由 JVM 调用,用于分发没有被捕获的异常。
由此我们知道,异常分发是以线程进行的,可以在 Thread 中对没有捕获的异常进行拦截和处理。用于处理异常的对象是通过 getUncaughtExceptionHandler 获取。那这个接口获取的是什么呢?


2. 捕获单个线程的异常
 

    用于处理异常的对象其实是一个实例化的接口,定义如下:

public interface UncaughtExceptionHandler {
      void uncaughtException(Thread t, Throwable e);
}

                                                                         (代码 2,来自 Thread.java)

      由上面 UncaughtExceptionHandler 定义可以知道,其实这个接口非常简单,只有一个回调方法。因此我们只需要自己实现一个 UncaughtExceptionHandler,重写 uncaughtException这一个方法就可以了。

// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ?
        uncaughtExceptionHandler : group;
}
/**
*Set the handler invoked when this thread abruptly terminates
* due to an uncaught exception.
*/

public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    checkAccess();
    uncaughtExceptionHandler = eh;
} 

                                                                    (代码 3,来自 Thread.java)
     

      通过调用一个 Thread 实例的 setUncaughtExceptionHandler 方法设置uncaughtExceptionHandler 这个属性就实现了当前线程的异常捕获器。
      至此,单个线程的异常捕获就可以实现了,由(代码 1)处接收到未捕获的异常,获取到当前设置的 uncaughtExceptionHandler,调用 uncaughtException 方法。
      仔细看 getUncaughtExceptionHandler()方法:

         uncaughtExceptionHandler != null ?uncaughtExceptionHandler : group;
   

     /* The group of this thread */
      private ThreadGroup group;

                                                              (代码 4,来自 Thread.java)

       也就是说,如果没有设置 uncaughtExceptionHandler 的话,返回 group,那这个 group是什么呢?


3. 捕获整个应用的异常


一般情况下,我们更多的需求是捕获整个应用里出现的异常。原理就是上面的 group。

public class ThreadGroup implements Thread.UncaughtExceptionHandler {

public void uncaughtException(Thread t, Throwable e) {
               。。。
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
               。。。
}
}

                                                                     (代码 5,来自 ThreadGroup .java)
       以上代码进行了简化。

       其实 ThreadGroup 也实现了 UncaughtExceptionHandler ,并且重写的 uncaughtException方法主要调用了 Thread 的静态方法 getDefaultUncaughtExceptionHandler。

      这又回到了 Thread。

// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
     defaultUncaughtExceptionHandler = eh;
 }
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
    return defaultUncaughtExceptionHandler;
}

                                                                      (代码 6,来自 Thread .java)

         可以看出,defaultUncaughtExceptionHandler 和代码 3 处非常类似,唯一区别是defaultUncaughtExceptionHandler 是静态变量,因此对应的 get 和 set 方法也成了静态方法。
         因此如果通过 Thread 的静态方法 setDefaultUncaughtExceptionHandler 设置defaultUncaughtExceptionHandler,那么实际上就是对当前线程的线程组以及包含的所有子线程设置了异常捕获器。
        如果我们要实现对整个应用的 Crash 异常捕获,那么只需要通过主线程设置defaultUncaughtExceptionHandler 就可以了。
       

        具体实现可以参考:
        Android 捕获异常日志: https://www.jianshu.com/p/d231e62e4e6c


4. 系统默认的 Crash 拦截器


      其实 Android 系统默认已经为所有的应用进程设置了 defaultUncaughtExceptionHandler。这个commonInit方法会在每个应用启动前被调用。

private static final void commonInit() {
    if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
    /* set default handler; this applies to all threads in the VM */
    Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
                   。。。

                                                (代码 7,来自 com.android.internal.os.RuntimeInit.java)
         

         接下来,我们看看系统默认的拦截器做了些什么?

private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
    public void uncaughtException(Thread t, Throwable e) {
                      。。。。。。
//片段1
            if (mApplicationObject == null) {
                Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
            } else {
                StringBuilder message = new StringBuilder();
                message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
                final String processName = ActivityThread.currentProcessName();
                if (processName != null) {
                    message.append("Process: ").append(processName).append(", ");
                }
                message.append("PID: ").append(Process.myPid());
                Clog_e(TAG, message.toString(), e);
            }

                      。。。。。。
            // Bring up crash dialog, wait for it to be dismissed
//片段2
            ActivityManagerNative.getDefault().handleApplicationCrash(
                    mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
        } 
                      。。。。。。
 finally {
            // Try everything to make sure this process goes away.
//片段3
            Process.killProcess(Process.myPid());
            System.exit(10);
        }
    }
}

                                                      (代码 8,来自 com.android.internal.os.RuntimeInit.java)

(1)片段 1,这不就是我们常常遇到 Crash 后打印的日志吗?对的,就是在这儿生成的。因此我们在检索一些应用层 Crash 日志的时候,常常使用“Fatal”作为关键字。如果错误日志格式不是这样,那就应该是用户自己处理的异常,或者自己设置了拦截器。

(2)片段 3,最终是要杀掉报 Crash 异常的进程的。这在我们自己写拦截器时,也是必须的。

(3)片段 2,将 Crash 信息报给 ActivityManagerNative,并最终传递给 AMS。AMS 中会弹出Crash dialog。此外 AMS 还会通过 DBMS(DropBoxManagerService)将错误日志做持久化存储。DBMS 也是系统统一收集管理各类异常的系统服务。


 在《【Android】【源码分析】系统各类异常日志收集服务(DropBoxManagerService)》中,主要分析了DBMS是如何收集各类系统异常的。

猜你喜欢

转载自blog.csdn.net/a953210725/article/details/107545366