ANR 分析与优化
问题
ANR 是什么?如何排查和避免 ANR?
答案
ANR 触发条件
| 类型 | 超时时间 | 说明 |
|---|---|---|
| Input 事件 | 5 秒 | 按键或触摸事件未响应 |
| BroadcastReceiver | 前台 10s / 后台 60s | onReceive 未完成 |
| Service | 前台 20s / 后台 200s | onCreate/onStartCommand 未完成 |
| ContentProvider | 10 秒 | publish 阶段超时 |
常见原因
排查方法
1. traces.txt 分析
# 获取 ANR 日志
adb pull /data/anr/traces.txt
# 或在 Android 11+ 使用
adb bugreport bugreport.zip
traces.txt 关键信息:
----- pid 12345 at 2024-01-01 12:00:00 -----
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0
| held mutexes=
at com.example.db.UserDao.queryAll(UserDao.java:42)
- waiting to lock <0x12345678> (a java.lang.Object) held by thread 15
at com.example.ui.MainActivity.onCreate(MainActivity.java:28)
关键看点:
- 主线程状态(
Blocked、Waiting、Runnable) - 被阻塞的位置(堆栈)
- 锁被哪个线程持有
2. Perfetto 分析
# 对于 ANR,Perfetto 可以看到主线程被阻塞的时间段
# 在 Perfetto UI 中搜索 "ANR" 标签
3. StrictMode 提前发现
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog() // 日志输出
.penaltyDeath() // 直接崩溃(激进)
.build()
)
}
解决方案
// ❌ 主线程操作数据库
fun loadData() {
val users = db.userDao().getAll() // ANR 风险!
adapter.submitList(users)
}
// ✅ 协程 + IO 调度
fun loadData() {
lifecycleScope.launch {
val users = withContext(Dispatchers.IO) {
db.userDao().getAll()
}
adapter.submitList(users)
}
}
常见面试问题
Q1: 如何在线上监控 ANR?
答案:
- ANR WatchDog:独立线程定期向主线程 post 消息,超时未响应则上报
- Firebase Performance:自动收集 ANR 数据
- Perfetto:Android 11+ 系统自动记录 ANR trace
- 自定义监控:通过
Choreographer.FrameCallback检测主线程卡顿
class ANRWatchDog : Thread() {
private val mainHandler = Handler(Looper.getMainLooper())
@Volatile private var tick = 0L
override fun run() {
while (!isInterrupted) {
tick = System.currentTimeMillis()
mainHandler.post { tick = 0L }
Thread.sleep(5000)
if (tick != 0L) {
// 主线程 5 秒未响应,上报 ANR
reportANR()
}
}
}
}
Q2: BroadcastReceiver 中耗时操作怎么处理?
答案:
使用 goAsync() 延长处理时间(但仍有 30s 限制),或启动 WorkManager 任务:
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val pendingResult = goAsync()
CoroutineScope(Dispatchers.IO).launch {
doHeavyWork()
pendingResult.finish()
}
}
}