ANR 简介
ANR(Application Not Responding),即应用程序无响应,Android 系统指定某些事件需要在规定时间内完成,如果超过预定时间还能未能得到有效响应,就会造成 ANR
常见 ANR 场景
系统 ANR 流程
埋炸弹 (9. 延时消息 SERVICE_TIMEOUT_MSG)
无 ANR 拆炸弹 (19. 移除消息 SERVICE_TIMEOUT_MSG)
有 ANR 爆炸弹 (10. 处理消息 appNotResponding)
系统处理消息
输出发生 ANR 进程的信息到 EventLog
如果是后台 ANR, 则只收集当前进程;如果是前台 ANR,则会收集其他进程。具体的收集顺序为:
发生 ANR 的进程
SystemServer 进程
持久化进程:比如输入法进程,电话进程,NFC 进程,系统网络进程,安全守护进程(脸部,指纹识别)等等
最近运行过进程(按照 CPU 使用率取排名前 5 进程)
依次对收集到的进程发送 SIGQUIT 信息,请求 Dump Trace 信息
Dump 总时长最多为 20 秒,超时则会中断 Dump 过程,仅输出已经收集到的进程信息
收集各相关进程 CPU 使用情况
输出 ANR Info 到 Logcat
输出 ANR Info 到 DropBox
如果是后台 ANR,系统会直接把进程杀掉,如果是前台 ANR,系统会设置进程 ANR 错误状态,显示 ANR 弹框
ANR 监控
感知 ANR
Linux 内核可以通过使用 sigaction 方法注册 signal handler 进行异步监听 SIGQUIT信号
通过 pthread_sigmask 或者 sigprocmask 把 SIGQUIT 设置为 UNBLOCK
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &sigSet, nullptr);
通过 sigaction 方法,建立一个 signal handler 进行异步监听
void signalHandler(int sig, siginfo_t* info, void* uc) {
if (sig == SIGQUIT) {
//Got An ANR
}
}
struct sigaction sa;
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
sigaction(SIGQUIT, &sa, nullptr);
在 signal handler 里面重新向 signal catcher 线程发送一个 SIGQUIT 信号,让系统正常走 ANR 流程
int tid = getSignalCatcherThreadId(); //遍历/proc/[pid]目录,找到SignalCatcher线程的tid
tgkill(getpid(), tid, SIGQUIT);
获取 ANR Trace 信息
通过 Native PLT Hook 方式 hook socket open / connect / write 方法获取系统 dump ANR trace
只会在接收到SIGQUIT信号后(重新发送SIGQUIT信号给Signal Catcher前)进行 hook
只处理 signal catcher 线程 open / connect 后的第一次 write
int (*original_connect)(int __fd, const struct sockaddr* __addr, socklen_t __addr_length);
int my_connect(int __fd, const struct sockaddr* __addr, socklen_t __addr_length) {
if (strcmp(__addr->sa_data, "/dev/socket/tombstoned_java_trace") == 0) {
isTraceWrite = true;
signalCatcherTid = gettid();
}
return original_connect(__fd, __addr, __addr_length);
}
int (*original_open)(const char *pathname, int flags, mode_t mode);
int my_open(const char *pathname, int flags, mode_t mode) {
if (strcmp(pathname, "/data/anr/traces.txt") == 0) {
isTraceWrite = true;
signalCatcherTid = gettid();
}
return original_open(pathname, flags, mode);
}
ssize_t (*original_write)(int fd, const void* const __pass_object_size0 buf, size_t count);
ssize_t my_write(int fd, const void* const buf, size_t count) {
if(isTraceWrite && signalCatcherTid == gettid()) {
isTraceWrite = false;
signalCatcherTid = 0;
char *content = (char *) buf;
printAnrTrace(content);
}
return original_write(fd, buf, count);
}
void hookAnrTraceWrite() {
int apiLevel = getApiLevel();
if (apiLevel < 19) {
return;
}
if (apiLevel >= 27) {
plt_hook("libcutils.so", "connect", (void *) my_connect, (void **) (&original_connect));
} else {
plt_hook("libart.so", "open", (void *) my_open, (void **) (&original_open));
}
if (apiLevel >= 30 || apiLevel == 25 || apiLevel ==24) {
plt_hook("libc.so", "write", (void *) my_write, (void **) (&original_write));
} else if (apiLevel == 29) {
plt_hook("libbase.so", "write", (void *) my_write, (void **) (&original_write));
} else {
plt_hook("libart.so", "write", (void *) my_write, (void **) (&original_write));
}
}
ANR Trace
// 进程号、时间戳、时区
----- pid 24078 at 2025-02-19 15:39:30.671716696+0800 -----
// 进程名
Cmd line: com.miravia.android.dev
// os 构建指纹
Build fingerprint: 'Xiaomi/lisa_global/lisa:13/TKQ1.220829.002/V14.0.7.0.TKOMIXM:user/release-keys'
// 处理器架构
ABI: 'arm64'
// 构建类型 debug|optimized
Build type: optimized
Debug Store:
// 总时间(Sum): 109.525毫;99%置信区间(99% C.I.);大多数时间挂起都发生在 4 微秒到 24299.520 微秒这一时间范围内;平均时间(Avg): 挂起的平均时间是897.745微秒;最大时间(Max): 最大挂起时间是28399微秒
suspend all histogram: Sum: 109.525ms 99% C.I. 4us-24299.520us Avg: 897.745us Max: 28399us
// 应用包含162个活跃线程
DALVIK THREADS (162):
// 主线程状态和堆栈
"main" prio=5 tid=1 Native
| group="main" sCount=1 ucsCount=0 flags=1 obj=0x71cd4ab0 self=0xb400007c72442c00
| sysTid=24078 nice=0 cgrp=background sched=0/0 handle=0x7d1dfc44f8 // 线程属于后台控制组
| state=S schedstat=( 1071297876235 50447682972 415777 ) utm=101560 stm=5569 core=0 HZ=100 // 当前线程状态为 "S"(睡眠)
| stack=0x7ff8e30000-0x7ff8e32000 stackSize=8188KB // 堆栈地址范围和大小
| held mutexes= // 持有的互斥锁,当前没有持有任何互斥锁
native: #00 pc 000e1b9c /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+12) (BuildId: 449f781894033dce6346794a1ee593e0)
native: #01 pc 00017e40 /system/lib64/libutils.so (android::Looper::pollInner+192) (BuildId: 104125701f0f8a41374b9998e94209e9)
native: #02 pc 00017d1c /system/lib64/libutils.so (android::Looper::pollOnce+116) (BuildId: 104125701f0f8a41374b9998e94209e9)
native: #03 pc 001660f8 /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce+48) (BuildId: 1809a12249dabac956d486048631a652)
native: #04 pc 00384370 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #05 pc 0036db74 /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #06 pc 003671e4 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+1928) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #07 pc 0076e170 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #08 pc 003869d8 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #09 pc 001ef900 /system/framework/framework.jar (android.os.MessageQueue.next)
native: #10 pc 00359650 /apex/com.android.art/lib64/libart.so (art::interpreter::Execute +428) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #11 pc 00367a78 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+4124) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #12 pc 0076e170 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #13 pc 003869d8 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #14 pc 001ee83c /system/framework/framework.jar (android.os.Looper.loopOnce)
native: #15 pc 00359650 /apex/com.android.art/lib64/libart.so (art::interpreter::Execute +428) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #16 pc 00367a78 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+4124) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #17 pc 0076e170 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #18 pc 003869d8 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #19 pc 001ef02c /system/framework/framework.jar (android.os.Looper.loop)
native: #20 pc 00359650 /apex/com.android.art/lib64/libart.so (art::interpreter::Execute +428) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #21 pc 00367a78 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+4124) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #22 pc 0076e170 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #23 pc 003869d8 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #24 pc 001c760c /system/framework/framework.jar (android.app.ActivityThread.main)
native: #25 pc 003589dc /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+1932) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #26 pc 00384498 /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+88) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #27 pc 0036de40 /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub+640) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #28 pc 003698f4 /apex/com.android.art/lib64/libart.so (_jobject* art::InvokeMethod<8>+732) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #29 pc 006c6738 /apex/com.android.art/lib64/libart.so (art::Method_invoke +32) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #30 pc 00384370 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+144) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #31 pc 0036db74 /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #32 pc 003671e4 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+1928) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #33 pc 0076e170 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+12208) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #34 pc 003869d8 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+8) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #35 pc 004f69e8 /system/framework/framework.jar (com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run)
native: #36 pc 003589dc /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+1932) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
native: #37 pc 00384498 /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+88) (BuildId: 3f7d5a016e08d528f129bdd336d81168)
at android.os.MessageQueue.nativePollOnce(Native method)
at android.os.MessageQueue.next(MessageQueue.java:341)
at android.os.Looper.loopOnce(Looper.java:168)
at android.os.Looper.loop(Looper.java:299)
at android.app.ActivityThread.main(ActivityThread.java:8252)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)
DumpLatencyMs: 39.0555
// ReferenceQueueDaemon 线程状态和堆栈
"ReferenceQueueDaemon" daemon prio=5 tid=6 Waiting
| group="system" sCount=1 ucsCount=0 flags=1 obj=0x15767058 self=0xb400007c423c5000
| sysTid=24086 nice=4 cgrp=background sched=0/0 handle=0x7c55719cb0
| state=S schedstat=( 413060873 112656828 239 ) utm=36 stm=4 core=2 HZ=100
| stack=0x7c55616000-0x7c55618000 stackSize=1039KB
| held mutexes=
at java.lang.Object.wait(Native method)
- waiting on <0x0f0c1d8f> (a java.lang.Class<java.lang.ref.ReferenceQueue>)
at java.lang.Object.wait(Object.java:405)
at java.lang.Object.wait(Object.java:543)
at java.lang.Daemons$ReferenceQueueDaemon.runInternal(Daemons.java:251)
- locked <0x0f0c1d8f> (a java.lang.Class<java.lang.ref.ReferenceQueue>)
at java.lang.Daemons$Daemon.run(Daemons.java:131)
at java.lang.Thread.run(Thread.java:1012)
DumpLatencyMs: 544.047
// 其他线程状态和堆栈
......
ANR 监控痛点及改进方案
误报
发生ANR的进程一定会收到 SIGQUIT 信号,但是收到 SIGQUIT 信号的进程并不一定发生了ANR
其他进程的 ANR:监控到SIGQUIT时,可能是监听到了其他进程产生的ANR,从而产生误报
非ANR发送 SIGQUIT:开发者和厂商都可以很容易的发送一个SIGQUIT(Java 层调用android.os.Process.sendSignal 方法;Native 层调用 kill 或者 tgkill 方法)
NOT_RESPONDING 标识
进程处于NOT_RESPONDING的状态可以确认该进程发生了ANR
监控到 SIGQUIT 信号后,在 20 秒内(20 秒是ANR dump 的 timeout 时间)不断轮询自己进程是否有 NOT_RESPONDING 标识,一旦发现有这个标识,那么马上就可以认定发生了一次 ANR
private static boolean checkErrorState() {
try {
Application application = sApplication == null ? Matrix.with().getApplication() : sApplication;
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){
MatrixLog.e(TAG,"[checkErrorState] error : %s", t.getMessage());
}
return false;
}
厂商自定义 ANR
在《手淘黑科技ANRHacker # MIUI自定义ANR》中提到主线程卡顿超过2.5s就会有一个系统 com.miui.daemon进程向APP发出 Signal 3(ANR信号),不仅是 MIUI 系统,其他国内厂商的系统也会有类似的逻辑,这会对现有CrashSDK的ANR监控有很大的干扰,其中一个表现是 EMAS 中 Unknown 类型 ANR 问题占比较大。
miui.mqsas.sdk.MQSEventManagerDelegate
如果 MQSEventManagerDelegate.getMQSService 返回 null 则不会上报 AppScoutEvent。
从上面的代码可以分析出:
需要强制将MQSEventManagerDelegate#mService设置为 null,可以通过反射实现。
需要让 miui.mqsas.IMQSService.asInterface 这个方法一直返回 null,可以传入 null 实现。
剩下的关键点在于如何让 ServiceManager.getService(MQS_SERVICE_NAME)强制返回 null?答案是通过 Java 动态代理代理 ServiceManager.getService 方法,然后判断入参值为MQS_SERVICE_NAME时直接返回 null
App 退出原因
发生ANR的进程并不一定会被设置为NOT_RESPONDING状态,如后台ANR,闪退 ANR。
Android 11 及以上版本提供了退出原因查询接口:ActivityManager#getHistoricalProcessExitReasons,其返回的是一个 ApplicationExitInfo 的列表,为系统保存的多次 App 退出的原因
进程退出原因分类
前后台状态
归因
由于系统判断出现 ANR 到系统通知进程执行 Dump trace 之间存在一定的时间差,期间进程主线程依然在执行 MessageQueue 消息 或 IdleHandler 任务,这往往会导致 ANR trace 抓取的堆栈并不是耗时任务的堆栈
MessageQueue 消息
通过代理 Looper 中的 mLogging 对象,监控消息消息的起始点和结束点,然后判断消息耗时是否超时
boolean proxyLogging() {
try {
// Android 10 以下代理Looper.mLogging
Field mLogging = ReflectUtils.getHiddenField(Looper.class, "mLogging");
if (mLogging == null) {
return false;
}
Printer printer = (Printer) mLogging.get(Looper.getMainLooper());
if (printer instanceof MainLooperTaskMonitor) {
return true;
}
Looper.getMainLooper().setMessageLogging(this);
originPrinter = printer;
return true;
} catch (Throwable e) {
TBPLogger.error(TAG, e, "proxyLogging exception");
}
return false;
}
// Android 10 及以上代理 Looper.sObserver
boolean proxyLooperObserver() {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Class<?>[] interfaces = new Class<?>[]{Class.forName("android.os.Looper$Observer")};
Object proxyInstance = Proxy.newProxyInstance(MainLooperTaskMonitor.class.getClassLoader(), interfaces, this);
Field sObserverField = ReflectUtils.getHiddenField(Looper.class, "sObserver");
if (sObserverField == null) {
return false;
}
originObserver = CommonUtils.replaceFieldValue(Looper.class, "sObserver", null, proxyInstance);
observerProxied = true;
return true;
}
} catch (Throwable e) {
TBPLogger.error(TAG, e, "proxyLooperObserver exception");
observerProxied = false;
}
return false;
}
IdleHandler 任务
通过反射替换 MessageQueue 原来的 mIdleHandlers 为自定义的 ArrayList
在自定义的 ArrayList 里重写 add 和 remove 方法,用自定义的 IdleHandlerProxy 包裹住业务方传入的 IdleHandler
MessageQueue 每次执行 queueIdle 方法,都会执行到我们的 IdleHandlerProxy
定时堆栈采集
如上图所示,堆栈采样的时间对齐方案具体实现如下:
独立的堆栈采样子线程负责堆栈的超时抓取
基于主线程的任务监听机制,每个任务的开始和结束都会告知到堆栈抓取模块
超时任务触发堆栈抓取的前提条件是:当前最新的任务耗时时长超过最低超时时间
执行完堆栈抓取后,会对超时时长进行渐进,再丢一个超时任务。直到当前任务执行完成
项目实践
ANR 误报消除
小米系统自定义 ANR 消除 (1.57.0 上线)
大盘 Android ANR 率 0.80% -> 0.20% (相对降低 -75%)
小米系统自定义 ANR 消除预估相对降低 -23% (小米系统 ANR 占比 46% * 50% 噪音)
ANR 率相对降低另外的 52% 可能是 UC 内核升级引起的;
后续方向
增强堆栈采集 ANR 归因能力
Input dispatching timed out 相关 ANR 治理
SharedPreferences 相关 ANR 治理