Android 线上监控方案

线上发生 卡顿/ANR/Crash 了怎么办

Posted by Hurshi on 2023.05.23

前置知识

Linux 知识

1. fork 进程
  1. fork 主进程后,子进程与主进程共享内存空间
  2. 子进程中不会有主进程中的线程信息;
2. 抓取堆栈
  1. 抓当前线程的堆栈,可以直接抓;
  2. 抓其他线程的堆栈,需要将目标线程挂起,抓完后再恢复;
3. hprof

监控

监控卡顿

  1. BlockCanary 方案

    主线程所有执行的任务都在 dispatchMessage 方法中派发执行;可以通过setMessageLogging()的方式,统计dispatchMessage执行前后的时间差,来判断是否有卡顿;

  2. Choreographer 方案

    推荐阅读:Choreographer原理SurfaceFlinger 绘图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
        @Override    
        public void doFrame(long frameTimeNanos) {
            if(frameTimeNanos - mLastFrameNanos > 100) {
                ...
            }
            Choreographer.getInstance().postFrameCallback(this);
        }
    });
    
  3. Matrix 方案

    修改字节码的方式,在编译期修改所有 class 文件中的函数字节码,对所有函数前后进行打点插桩。

  4. Sliver 方案

    指定时间间隔对堆栈进行采样,对比前后方法堆栈,获取方法耗时

    好处:采样频率固定,可以监控到包括 framework 层的所有信息,相比于 Matrix 的插桩有更高的性能;

监控 ANR

  1. 监听 UI 线程 Handler

  2. Matrix 方案

    1. 监听 SIGQUIT 信号(但监听到了 SIGQUIT 不一定表示发生了 ANR )

    2. 获取 NOT_RESPONDING 标记(有这个标记肯定发生了 ANR,但有些 ANR 没有这个标记)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
      private static boolean checkErrorState() {
          try {
              ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
              List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
              if (procs == null) return false;
              for (ActivityManager.ProcessErrorStateInfo proc : procs) {
                  if (proc.pid != android.os.Process.myPid()) continue;
                  if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
                  return true;
              }
              return false;
          } catch (Throwable t){
          }
          return false;
      }
      
    3. 收到 SIGQUIT 后,检查 MainLooper 中正在处理的消息时常

      1
      2
      3
      4
      
      if (Looper.getMainLooper().getQueue().mMessages.getWhen()
          - SystemCkock.uptimeMillis() > TIME_THRESHOLD){
         // ANR
      }
      
  3. xCrash 方案

    监听并拦截 SIG 信号,然后调用 Runtime::DumpForSigQuit 方法,让执行 TraceDumper 步骤,以生成 traces.txt 文件;

监控 Crash

  1. Java Crash

    1
    2
    3
    4
    5
    6
    
    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
       
        }
    });
    
  2. Native Crash

    xCrash 方案:干货-安卓APP崩溃捕获方案——xCrash

    1. 监听 SIG 信号

      1. Kernel 发出的

        SIGFPE: 除数为零。

        SIGILL: 无法识别的 CPU 指令。

        SIGSYS: 无法识别的系统调用(system call)。

        SIGSEGV: 错误的虚拟内存地址访问。

        SIGBUS: 错误的物理设备地址访问。

      2. 用户态进程发出

        SIGABRT: 调用 abort()/ kill() / tkill() / tgkill() 自杀,或被其他进程通过 kill() / tkill() / tgkill() 他杀。

    2. 可能会遇到的极端情况

      栈溢出、堆内存不可用、虚拟内存地址耗尽、FD 耗尽、Flash 空间耗尽等

    3. 收到“信号”,尽快“逃逸”

      要尽快在信号处理函数中执行“逃逸”,即使用clone() + execl() 创建新的子进程,然后在子进程中继续收集崩溃信息

    4. 使用 ptrace() Suspend 崩溃进程中所有的线程。

采集

采集堆栈

采样型

  • 原理:定时去获取指定线程的堆栈信息

  • 方案:

    1. Debug.startMethodTracingSampling

      在独立进程循环向主进程抓栈,抓栈前需要 SuspendAll,抓完后再 ResumeAll,比较消耗性能;

    2. Profilo

      定时发送 SIGPROF 信号,目标线程可以直接在内部抓,损耗较低;但兼容性较差;

    3. Sliver

埋点型

  • 原理:在函数执行的出入口记录时间和方法名
  • 方案:
    1. Systrace
    2. Debug.startMethodTracing
    3. Nanoscope
    4. 字节码插桩

采集内存

  • fork 主进程,在子进程采集 hprof 数据

采集 ANR

  • xCrash 方案:调用 Runtime::DumpForSigQuit 打印了 ANR 日志
  • Matrix 方案:监听到 ANR 后,Hook 住 socket 的 write 方法,就能拿到系统 dump 的 ANR Trace 内容;

三方方案介绍

Sliver

参考

  1. 西瓜视频稳定性治理体系建设三:Sliver 原理及实践
  2. 干货-安卓APP崩溃捕获方案——xCrash
  3. 字节跳动技术团队 # ANR
  4. 微信Android客户端的ANR监控方案
  5. Android 性能优化必知必会
  6. Android 重学系列 SurfaceFlinger的概述
  7. Android 系统架构 —— SurfaceFlinger VSYNC 信号处理
  8. Matrix - ANR 原理解析
  9. 流畅性三板斧番外之:各大厂与卡顿和ANR的战斗记录