一、ANR 基础认知
ANR (Application Not Responding) 是指应用程序在主线程执行耗时操作导致无法响应用户输入的事件(5秒内未响应输入事件或BroadcastReceiver在10秒内未执行完毕)。
常见触发场景
-
主线程进行网络请求
-
主线程执行大量数据库操作
-
主线程进行复杂计算
-
主线程等待锁或其它线程
-
系统资源紧张导致主线程无法获取CPU时间片
二、ANR 线上监控方案
1. 文件监控法(推荐)
Android系统会将ANR信息记录到以下位置:
/data/anr/traces.txt
/data/anr/anr_*
实现方案:
public class ANRFileObserver extends FileObserver {
public ANRFileObserver() {
super("/data/anr/", FileObserver.CLOSE_WRITE);
}
@Override
public void onEvent(int event, String path) {
if (path != null && path.startsWith("anr_")) {
// 读取ANR文件内容并上报
reportANR(readANRFile("/data/anr/" + path));
}
}
// 注册监控
public static void startWatching() {
new ANRFileObserver().startWatching();
}
}
注意事项:
-
需要READ_LOGS权限
-
Android 7.0+需要特殊处理
-
不同厂商可能有路径差异
2. 消息队列监控法
public class ANRWatchDog extends Thread {
private static final int ANR_THRESHOLD = 5000;
private volatile long mLastTick = 0;
private volatile boolean mShouldStop = false;
@Override
public void run() {
while (!mShouldStop) {
mLastTick = SystemClock.uptimeMillis();
// 主线程重置计数器
mHandler.post(() -> mLastTick = SystemClock.uptimeMillis());
SystemClock.sleep(ANR_THRESHOLD);
if (SystemClock.uptimeMillis() - mLastTick >= ANR_THRESHOLD
&& !mShouldStop) {
// 获取主线程堆栈
String stackTrace = getMainThreadStackTrace();
// 上报ANR
reportANR(stackTrace);
}
}
}
private String getMainThreadStackTrace() {
for (Map.Entry<Thread, StackTraceElement[]> entry :
Thread.getAllStackTraces().entrySet()) {
if (entry.getKey().getId() == Looper.getMainLooper().getThread().getId()) {
return Arrays.toString(entry.getValue());
}
}
return "";
}
}
3. 使用第三方解决方案
-
Bugly:腾讯提供的ANR监控
-
Firebase Crashlytics:Google的ANR监控
-
Matrix:微信开源的APM框架,包含ANR监控
-
Hertz:字节跳动的性能监控框架
三、ANR 日志分析要点
1. 关键信息提取
典型ANR日志包含:
----- pid 12345 at 2023-01-01 12:00:00 -----
Cmd line: com.example.app
...
DALVIK THREADS (12):
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x72e9f8a8 self=0x7f88e14c00
| sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x7f96a84e98
| state=D schedstat=( 123456789 987654321 1234 ) utm=12 stm=34 core=1 HZ=100
| stack=0x7fc15a8000-0x7fc15aa000 stackSize=8MB
| held mutexes=
at java.lang.Object.wait(Native Method)
- waiting on <0x0cd5f3f2> (a java.lang.Object)
at java.lang.Thread.parkFor$(Thread.java:2137)
- locked <0x0cd5f3f2> (a java.lang.Object)
at sun.misc.Unsafe.park(Unsafe.java:358)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:868)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1021)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1328)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:232)
at com.example.app.MainActivity$1.run(MainActivity.java:56)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
2. 分析步骤
-
确认ANR时间点:查看日志头部时间戳
-
定位主线程状态:查找"main"线程
-
分析堆栈调用:重点关注应用自身的代码
-
检查锁信息:查看"locked"和"waiting on"部分
-
查看CPU负载:检查"schedstat"和"utm/stm"值
-
检查内存信息:查看是否有low memory警告
四、ANR 预防与优化
1. 代码层面优化
-
避免主线程阻塞操作:
// 错误示例 public void loadData() { String data = networkRequest(); // 同步网络请求 updateUI(data); } // 正确示例 public void loadData() { new Thread(() -> { String data = networkRequest(); runOnUiThread(() -> updateUI(data)); }).start(); }
-
优化数据库操作:
// 使用异步查询 Cursor cursor = db.rawQuery("SELECT * FROM table", null); // 改为 CursorLoader loader = new CursorLoader(context, uri, projection, selection, selectionArgs, sortOrder);
2. 架构层面优化
-
使用IntentService处理长时间后台任务
-
采用WorkManager管理后台任务
-
实现任务优先级队列
-
使用RxJava或Coroutines管理异步任务
3. 监控体系建设
-
建立ANR分级机制:
-
关键路径ANR(如启动、支付) - P0
-
普通页面ANR - P1
-
后台任务ANR - P2
-
-
ANR聚合分析:
-- 示例:按堆栈特征聚合ANR SELECT stack_signature, COUNT(*) as count, AVG(duration) as avg_duration FROM anr_logs GROUP BY stack_signature ORDER BY count DESC
五、厂商适配注意事项
-
华为EMUI:
-
增加了严格的后台限制
-
需要加入自启动白名单
-
-
小米MIUI:
-
电源管理策略更激进
-
需要在"神隐模式"中配置
-
-
OPPO ColorOS:
-
应用速冻功能可能导致ANR
-
需要加入"允许后台运行"列表
-
-
VIVO FuntouchOS:
-
内存清理机制严格
-
需要加入后台高耗电例外列表
-
六、高级调试技巧
-
使用StrictMode检测潜在问题:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build());
-
Systrace分析ANR:
python systrace.py -a com.example.app -b 16384 -o trace.html \ sched freq idle am wm gfx view binder_driver
-
模拟ANR场景测试:
// 在测试代码中人为制造ANR @Test public void testANRHandling() { ANRWatchDog anrWatchDog = new ANRWatchDog(); anrWatchDog.start(); // 主线程休眠6秒模拟ANR SystemClock.sleep(6000); assertTrue(anrWatchDog.isANRDetected()); }
通过以上系统化的监控、分析和优化方案,可以有效降低线上ANR率,提升用户体验。建议结合业务特点选择适合的监控方案,并建立长期的ANR治理机制。