ANR 的原因与排查
mediumANR主线程StrictMode性能优化
什么是 ANR
ANR(Application Not Responding):应用无响应。当 Android 系统检测到应用主线程在规定时间内没有响应事件时,弹出 ANR 对话框。
ANR 超时阈值:
- 前台 Activity:用户操作(触摸、按键)5 秒无响应
- 前台 Service:20 秒内未完成启动(
onStartCommand) - 后台 Service:200 秒
- BroadcastReceiver:前台广播 10 秒,后台广播 60 秒
- ContentProvider:publish 超时 10 秒
ANR 的根本原因
主线程(UI 线程)执行了耗时操作:
// 以下任何操作放在主线程都极可能导致 ANR
// 1. 网络请求(最常见)
val response = URL("https://api.example.com").readText() // ❌ 网络 I/O 阻塞主线程
// 2. 数据库查询
val users = db.userDao().getAllUsers() // ❌ 复杂查询可能耗时数百毫秒
// 3. 文件读写
val content = File("/sdcard/large_file.txt").readText() // ❌ 大文件读取
// 4. 主线程与后台线程的锁竞争
synchronized(lock) {
// 如果后台线程持有 lock 且耗时很长
// 主线程在这里阻塞等待
}
// 5. 耗时的 CPU 计算
val result = fibonacci(50) // ❌ 在主线程做大量计算
另一种 ANR:主线程消息处理积压
主线程的 MessageQueue 中消息太多(频繁 UI 更新、动画),每条消息处理都正常,但累积起来超时:
RecyclerView 滑动时,每次 onBindViewHolder 都进行大量字符串拼接/图片解码
→ 每帧绑定耗时 8ms(超过 16ms 目标帧时间)
→ 动画卡顿,极端情况下触发 ANR
排查 ANR
traces.txt 分析
ANR 发生时,系统会生成 /data/anr/traces.txt(Android 11+ 在 /data/anr/anr_*):
# 通过 adb 获取
adb pull /data/anr/traces.txt
重点看 main 线程的调用栈:
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x12345678 self=0x...
| sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x...
| state=S schedstat=( ... ) utm=... stm=... core=3 HZ=100
| stack=0x... stackSize=8192KB
at com.example.app.SomeClass.doSomething(SomeClass.kt:42) ← 问题所在
- waiting to lock <0x00000012345> (a java.lang.Object) ← 等待锁
at com.example.app.SomeClass.onResume(SomeClass.kt:25)
at android.app.Activity.performResume(Activity.java:...)
...
关键标志:
Blocked:线程等锁,找持有锁的线程Sleeping:线程在 sleep,检查是否Thread.sleep()在主线程MONITOR:死锁相关
StrictMode(开发阶段预防)
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
// 检测主线程上的慢操作
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads() // 检测主线程磁盘读
.detectDiskWrites() // 检测主线程磁盘写
.detectNetwork() // 检测主线程网络请求
.detectCustomSlowCalls() // 检测自定义慢调用
.penaltyLog() // 输出日志
.penaltyDeath() // 开发阶段直接崩溃(比 ANR 更易发现)
.build()
)
// 检测内存泄漏(Cursor 未关闭、SQLite 对象泄漏等)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects()
.detectLeakedSqlLiteObjects()
.penaltyLog()
.build()
)
}
}
}
用协程将耗时操作移出主线程
class MainActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
fun loadData() {
// ❌ 旧写法:主线程阻塞
val data = repository.getData()
showData(data)
// ✓ 正确:协程 + 合适的 Dispatcher
lifecycleScope.launch {
// withContext 切换到 IO 线程池
val data = withContext(Dispatchers.IO) {
repository.getData() // 网络/数据库操作在 IO 线程
}
showData(data) // 回到主线程更新 UI
}
}
}
常见 ANR 场景及修复
| 场景 | 原因 | 修复 |
|---|---|---|
| 点击无响应 | DB/网络在主线程 | Dispatchers.IO + 协程 |
| Activity 启动慢 | onCreate 做太多初始化 |
懒加载、异步初始化 |
| 动画卡顿 | onBindViewHolder 耗时 |
减少 bind 逻辑,预计算数据 |
| 广播超时 | onReceive 做耗时操作 |
启动 IntentService/协程 |
| Service ANR | onStartCommand 过慢 |
用 WorkManager 代替长时间 Service |
ANR 与 OOM 的区别
- ANR:应用没死,只是主线程卡住了,修复后可以继续使用
- OOM(OutOfMemoryError):内存不足,应用崩溃,需要释放内存或修复泄漏
两者都需要避免,但 ANR 通常更难发现(用户会等待后放弃,而不是看到崩溃日志)。