深入浅出安卓ANR(应用无响应)全解析
一、ANR到底是什么?
ANR(Application Not Responding)就是安卓系统的"卡死警报",相当于手机在说:"这个App不理我了!"。当应用主线程被阻塞超过规定时间,系统就会弹出ANR弹窗。
ANR触发阈值
场景 | 时间阈值 |
---|---|
前台Activity/Service | 5秒 |
前台广播(BroadcastReceiver) | 10秒 |
后台Service | 20秒 |
ContentProvider响应 | 10秒 |
二、为什么会出现ANR?
主线程在忙什么?
主线程就像餐厅唯一的服务员,要同时:
- 处理用户点击(点菜)
- 更新UI(上菜)
- 执行代码逻辑(做菜)
如果让服务员去后厨炒菜(主线程做耗时操作),前台就会没人响应(ANR)。
常见ANR原因
pie
title ANR原因分布
"主线程IO操作" : 35
"死锁" : 25
"过度布局/绘制" : 20
"Binder调用阻塞" : 15
"其他" : 5
三、如何检测ANR?
1. 开发阶段工具
(1) StrictMode(严苛模式)
java
// 在Application中启用
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads() // 检测磁盘读
.detectDiskWrites() // 检测磁盘写
.detectNetwork() // 检测网络
.penaltyLog() // 违规时打印日志
.build());
(2) BlockCanary
gradle
// build.gradle
dependencies {
implementation 'com.github.markzhai:blockcanary-android:1.5.0'
}
java
// Application初始化
BlockCanary.install(this, new AppBlockCanaryContext()).start();
2. 线上监控方案
(1) ANRWatchDog
java
// 简易版实现
public class ANRWatchDog extends Thread {
@Override
public void run() {
while (!isInterrupted()) {
long lastRespondTime = SystemClock.uptimeMillis();
// 在主线程发送ping
new Handler(Looper.getMainLooper()).post(() -> {
lastRespondTime = SystemClock.uptimeMillis();
});
sleep(5000);
if (SystemClock.uptimeMillis() - lastRespondTime > 5000) {
// 上报ANR堆栈
}
}
}
}
(2) 系统日志分析
bash
# 获取ANR日志
adb pull /data/anr/traces.txt
四、典型ANR案例解析
案例1:数据库主线程查询
java
// 错误代码
fun loadUserData() {
val cursor = database.rawQuery("SELECT * FROM users", null) // 主线程查询
// ...
}
解决方案:
kotlin
// 使用Room+协程
@Dao
interface UserDao {
@Query("SELECT * FROM users")
suspend fun getUsers(): List<User>
}
// ViewModel中调用
viewModelScope.launch {
val users = userDao.getUsers() // 自动切IO线程
_userList.postValue(users) // 切回主线程更新UI
}
案例2:SharedPreferences同步写入
java
// 错误用法
sharedPreferences.edit()
.putString("key", "value")
.commit(); // 同步提交
正确做法:
java
sharedPreferences.edit()
.putString("key", "value")
.apply(); // 异步提交
案例3:死锁场景
java
// 线程A
synchronized(lockA) {
Thread.sleep(1000);
synchronized(lockB) { ... }
}
// 线程B(主线程)
synchronized(lockB) {
updateUI();
synchronized(lockA) { ... } // ANR!
}
解决方案:
- 使用
ReentrantLock.tryLock()
设置超时 - 统一加锁顺序
五、ANR优化进阶技巧
1. 主线程优化金字塔
graph TD
A[避免主线程耗时操作] --> B[使用异步框架]
B --> C[优化任务调度]
C --> D[减少UI重绘]
D --> E[内存优化]
2. 异步方案选型
方案 | 适用场景 | 特点 |
---|---|---|
AsyncTask | 简单异步任务 | 已废弃 |
HandlerThread | 串行任务队列 | 需手动管理 |
RxJava | 复杂异步流 | 学习曲线陡 |
协程 | 现代Kotlin首选 | 结构化并发 |
3. 关键优化点
- 布局优化:减少嵌套,使用ConstraintLayout
- 图片加载:Glide/Picasso自动切线程
- 网络请求:Retrofit+协程
- 数据库:Room+Flow
- 广播接收 :使用
goAsync()
或切线程处理
六、ANR日志分析实战
1. 解读traces.txt
ini
----- pid 12345 at 2023-01-01 12:00:00 -----
Cmd line: com.example.app
...
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x12c12345
| held mutexes=
at com.example.app.MainActivity$1.onClick(MainActivity.java:123)
- waiting to lock <0x0456789> (a java.lang.Object) held by thread 3
关键信息:
waiting to lock
:等待哪个锁held by thread
:锁被哪个线程持有
2. systrace分析
bash
# 生成systrace
python systrace.py -o trace.html -a com.example.app sched freq idle am wm gfx view
查看主线程的Wall duration
(实际执行时间)
七、ANR防护体系
1. 开发规范
- 主线程只做UI更新
- 所有IO操作必须异步
- 避免同步Binder调用
- 谨慎使用第三方库(检查是否会在主线程操作)
2. 监控体系
graph LR
A[客户端] --> B{ANR检测}
B -->|正常| C[继续运行]
B -->|异常| D[上传堆栈]
D --> E[服务端聚合]
E --> F[自动告警]
E --> G[智能归因]
3. 容灾方案
- 设置关键操作超时
java
Future<Void> future = executor.submit(task);
try {
future.get(3, TimeUnit.SECONDS);
} catch (TimeoutException e) {
future.cancel(true);
// 降级处理
}
- 重要服务使用独立进程
总结
- ANR本质:主线程被阻塞超过阈值
- 三大防线 :
- 预防(编码规范)
- 检测(工具监控)
- 治理(优化方案)
- 核心原则:主线程只做UI相关轻量操作
- 终极目标:让用户永远看不到ANR弹窗
掌握这些技巧,你的App将告别卡顿,流畅如飞! 🚀