前置知识
Linux 知识
1. fork 进程
- fork 主进程后,子进程与主进程共享内存空间
- 子进程中不会有主进程中的线程信息;
2. 抓取堆栈
- 抓当前线程的堆栈,可以直接抓;
- 抓其他线程的堆栈,需要将目标线程挂起,抓完后再恢复;
3. hprof
监控
监控卡顿
-
BlockCanary 方案
主线程所有执行的任务都在
dispatchMessage
方法中派发执行;可以通过setMessageLogging()
的方式,统计dispatchMessage
执行前后的时间差,来判断是否有卡顿; -
Choreographer 方案
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); } });
-
修改字节码的方式,在编译期修改所有 class 文件中的函数字节码,对所有函数前后进行打点插桩。
-
指定时间间隔对堆栈进行采样,对比前后方法堆栈,获取方法耗时
好处:采样频率固定,可以监控到包括
framework
层的所有信息,相比于 Matrix 的插桩有更高的性能;
监控 ANR
-
监听 UI 线程 Handler
-
-
监听 SIGQUIT 信号(但监听到了 SIGQUIT 不一定表示发生了 ANR )
-
获取 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; }
-
收到 SIGQUIT 后,检查 MainLooper 中正在处理的消息时常
1 2 3 4
if (Looper.getMainLooper().getQueue().mMessages.getWhen() - SystemCkock.uptimeMillis() > TIME_THRESHOLD){ // ANR }
-
-
监听并拦截 SIG 信号,然后调用
Runtime::DumpForSigQuit
方法,让执行 TraceDumper 步骤,以生成 traces.txt 文件;
监控 Crash
-
Java Crash
1 2 3 4 5 6
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { } });
-
Native Crash
xCrash 方案:干货-安卓APP崩溃捕获方案——xCrash
-
监听 SIG 信号
-
Kernel 发出的
SIGFPE: 除数为零。
SIGILL: 无法识别的 CPU 指令。
SIGSYS: 无法识别的系统调用(system call)。
SIGSEGV: 错误的虚拟内存地址访问。
SIGBUS: 错误的物理设备地址访问。
-
用户态进程发出
SIGABRT: 调用
abort()/ kill() / tkill() / tgkill()
自杀,或被其他进程通过kill() / tkill() / tgkill()
他杀。
-
-
可能会遇到的极端情况
栈溢出、堆内存不可用、虚拟内存地址耗尽、FD 耗尽、Flash 空间耗尽等
-
收到“信号”,尽快“逃逸”
要尽快在信号处理函数中执行“逃逸”,即使用
clone() + execl()
创建新的子进程,然后在子进程中继续收集崩溃信息 -
使用 ptrace() Suspend 崩溃进程中所有的线程。
-
采集
采集堆栈
采样型
-
原理:定时去获取指定线程的堆栈信息
-
方案:
-
Debug.startMethodTracingSampling
在独立进程循环向主进程抓栈,抓栈前需要 SuspendAll,抓完后再 ResumeAll,比较消耗性能;
-
Profilo
定时发送 SIGPROF 信号,目标线程可以直接在内部抓,损耗较低;但兼容性较差;
-
Sliver
-
埋点型
- 原理:在函数执行的出入口记录时间和方法名
- 方案:
- Systrace
- Debug.startMethodTracing
- Nanoscope
- 字节码插桩
采集内存
- fork 主进程,在子进程采集 hprof 数据
采集 ANR
- xCrash 方案:调用
Runtime::DumpForSigQuit
打印了 ANR 日志 - Matrix 方案:监听到 ANR 后,Hook 住 socket 的 write 方法,就能拿到系统 dump 的 ANR Trace 内容;