• android崩溃系列-崩溃原理分析


    结论

    在java默认的异常处理机制中,是没有崩溃退出这个说法的,而在android中的RuntimeInit对其拦截并且处理。

    源码分析

    1. 首先关注Thread类中的dispatchUncaughtException,JVM在处理未经捕获的异常时,会调用当前dispatchUncaughtException函数进行处理,这个里面我们能看到一个类型为UncaughtExceptionHandler的类。
    //java/lang/Thread.java
    //源码备注翻译:将未捕获的异常分派给处理程序。此方法旨在仅由运行时和测试调用。
    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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 我们来跟踪一下getUncaughtExceptionHandler()这个方法,如果没有设置uncaughtExceptionHandler,将使用线程所在的线程组来处理这个未捕获异常。线程组ThreadGroup实现了UncaughtExceptionHandler,所以可以用来处理未捕获异常。

      //java/lang/Thread.java
      //源码备注翻译:返回当此线程由于未捕获的异常而突然终止时调用的处理程序。如果该线程没有显式设置未捕获的异常处理程序,则返回该线程的ThreadGroup对象,除非该线程已终止,在这种情况下返回null 。
      public UncaughtExceptionHandler getUncaughtExceptionHandler() {
              return uncaughtExceptionHandler != null ?
                  uncaughtExceptionHandler : group;
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. 默认情况下,线程组处理未捕获异常的逻辑时,首先将异常消息通知给父线程组然后尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常如果没有默认的异常处理器则将错误信息输出到System.err(但是android在初始化时在RuntimeInit对uncaughtExceptionHandler进行的初始化),也同时可以知道此时应用并没有直接退出。

      //java/lang/ThreadGroup.java
      //官方注释翻译:
      /**当此线程组中的线程由于未捕获的异常而停止并且该线程没有设置特定的UncaughtExceptionHandler时,由 Java 虚拟机调用。
      此方法做了以下几个操作:
      1. 如果此线程组有父线程组,则使用相同的两个参数调用该父线程的 uncaughtException 方法。
      2. 否则,此方法会检查是否设置了 默认未捕获异常处理程序,如果是,则使用相同的方法调用其 uncaughtException 方法
      3. 否则,此方法确定 Throwable 参数是否是 {@link ThreadDeath} 的实例。如果是这样,没有什么特别的。否则,一条包含线程名称的消息(从线程的 {@link ThreadgetName getName} 方法返回)和使用 Throwable 的 {@link ThrowableprintStackTrace printStackTrace} 方法的堆栈回溯打印到{@linkplain Systemerr 标准错误流}。
      
      
      **/
      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);
                  }
              }
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
    3. 思考下,uncaughtExceptionHandler在为空的情况下再回采用默认的方式ThreadGroup处理,在ThreadGroup处理时,它首先通过getDefaultUncaughtExceptionHandler来处理,但是在Thread中我们看到了他对外提供了对应的设置函数如下setDefaultUncaughtExceptionHandler()

      并且我们知道,程序在出现错误后会有退出程序的操作,但是目前我们并没有看见退出程序的操作,我们继续跟踪分析。

      //java/lang/Thread.java
      /**
      设置当线程由于未捕获的异常而突然终止时调用的默认处理程序,并且没有为该线程定义其他处理程序。
      
      **/
      public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
               defaultUncaughtExceptionHandler = eh;
           }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    4. 来我们用AndroidStudio看看setDefaultUncaughtExceptionHandler调用,我们发现了RuntimtInit类以及KillApplicationHandler类(是不是很激动)。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cc9JzaVJ-1662304608259)(default调用)]

    5. RunTimeInit是由Zygote调用的,其初始化会初始化异常相关的操作。

      //com/android/internal/os/RuntimeInit.java 
      public static final void main(String[] argv) {
              enableDdms();
              if (argv.length == 2 && argv[1].equals("application")) {
                  if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
                  redirectLogStreams();
              } else {
                  if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
              }
      
              //注意此方法
              commonInit();
      
              /*
               * Now that we're running in interpreted code, call back into native code
               * to run the system.
               */
              nativeFinishInit();
      
              if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
          }
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    6. 我们继续分析commitInit方法,这里注意,我们能直观的在此处看到当前线程中设置了setDefaultUncaughtExceptionHandler—>其类型为KillApplicationHandler

        protected static final void commonInit() {
              if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
      
              /*
               * set handlers; these apply to all threads in the VM. Apps can replace
               * the default handler, but not the pre handler.
               */
              LoggingHandler loggingHandler = new LoggingHandler();
              Thread.setUncaughtExceptionPreHandler(loggingHandler);
            //此处是关键
              Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
      
              /*
               * Install a TimezoneGetter subclass for ZoneInfo.db
               */
              TimezoneGetter.setInstance(new TimezoneGetter() {
                  @Override
                  public String getId() {
                      return SystemProperties.get("persist.sys.timezone");
                  }
              });
              TimeZone.setDefault(null);
      ....优雅的省略号....
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    7. 此时我们看到了KillApplicationHandler,此类是android内部默认初始化设置的主线程异常处理方案,由此我们可以看见Android中怎样处理异常的。

      看uncaughtException的逻辑我们可以很明确的看到

      1. 他直接提取的是当前ActivityThread的当前线程,然后直接stopProFiling;
      2. 上报AMS崩溃异常信息;
      3. 在最终的finally中直接kill掉进程,且退出当前进程
      //com/android/internal/os/RuntimeInit.java 
      @Override
              public void uncaughtException(Thread t, Throwable e) {
                  try {
                      ensureLogging(t, e);
      
                      // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
                      if (mCrashing) return;
                      mCrashing = true;
      
                      // Try to end profiling. If a profiler is running at this point, and we kill the
                      // process (below), the in-memory buffer will be lost. So try to stop, which will
                      // flush the buffer. (This makes method trace profiling useful to debug crashes.)
                      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);
                  }
              }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
    8. 到此我们可以明确的知道导致崩溃的原因,到此我们先阶段总结一下:

      • JVM通过调用dispatchUncaughtException来进行未捕获异常处理
      • 具体对应的提供处理能力的是UncaughtExceptionHandler这个类
      • 默认ThreadGroup提供日志打印处理
      • 但是在进程环境初始化时RuntimeInit提供杀死进程的能力
      • 注意:既然他已经对外提供了设置UncaughtExceptionHandler的能力,那么我们自己可以编写Handler处理类来进行未捕获异常处理

    使用

    收集log便于分析:

    在Application中设置Handler

    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, Throwable ex) {
                //这里处理记录上传log,并记得kill进程并退出。
            }
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    优雅的处理:由于Thread中的defaultUncaughtExceptionHandler是静态的,当我们设置setDefaultUncaughtExceptionHandler()方法时,会把之前的设置的Handler替换掉,造成别人设置的失效,所以优雅的处理方式先调用getDefaultUncaughtExceptionHandler()方法将对象缓存起来后,再讲我们的Handler设置进去,当收到异常消息后,再通过保存的Handler消息将消息分发出去。

  • 相关阅读:
    微调语言大模型选LoRA还是全参数?基于LLaMA 2深度分析
    extern “C“的底层原理和一些思考,C/C++之间的相互调用
    关于JS进行大文件分片上传碰到的问题
    深入理解 python 虚拟机:字节码灵魂——Code obejct
    每天一道算法题の回文日期
    Java虚拟机:Java模块化系统
    内网安全-域横向批量at&schtasks&impacket
    【Matplotlib绘制图像大全】(七):Matplotlib使用xlim()和ylim()修改轴线刻度
    springboot整合elasticsearch
    【达摩院OpenVI】几行代码,尽享丝滑视频观感
  • 原文地址:https://blog.csdn.net/chendeshan330/article/details/126696409