Android随笔-ANR
一、ANR 是什么
ANR(Application Not Responding,应用无响应)是 Android 系统的一种自我保护机制:当主线程(UI Thread)在规定时间内无法响应用户输入或系统事件时,系统会弹出"应用无响应"对话框,提示用户选择"等待"或"关闭应用"。
本质上,ANR 是主线程被阻塞,导致消息队列(MessageQueue)中的事件无法被及时处理。
二、ANR 的判定标准(超时阈值)
| 场景 | 超时时间 | 触发条件 |
|---|---|---|
| 输入事件 ANR | 5 秒 | 按键、触摸等输入事件未被处理 |
| Broadcast ANR | 10 秒(前台)/ 60 秒(后台) | onReceive()执行超时 |
| Service ANR | 20 秒 | onCreate()/onStartCommand()执行超时 |
| ContentProvider ANR | 10 秒 | onCreate()执行超时 |
注意:这些超时时间是硬编码在系统源码中的,应用层无法修改。
三、哪些场景会产生 ANR
1. 主线程执行耗时操作(最常见)
// ❌ 错误示例:主线程直接网络请求@OverridepublicvoidonClick(Viewv){// 阻塞主线程,超过5秒就会ANRStringresult=HttpUtils.get("https://api.example.com/data");textView.setText(result);}其他常见耗时操作:
- 网络请求(HTTP、Socket)
- 数据库读写(SQLite)
- 文件 I/O(读写大文件、SharedPreferences 的
commit()) - 大量数据计算(复杂算法、图片编解码)
sleep()或wait()长时间阻塞
2. 主线程死锁
// ❌ 错误示例:主线程死锁privatefinalObjectlockA=newObject();privatefinalObjectlockB=newObject();// 线程1newThread(()->{synchronized(lockA){try{Thread.sleep(100);}catch(Exceptione){}synchronized(lockB){/* do something */}}}).start();// 主线程publicvoidonClick(Viewv){synchronized(lockB){synchronized(lockA){// 死锁!主线程永远等不到lockAtextView.setText("done");}}}3. BroadcastReceiver 中执行耗时操作
// ❌ 错误示例:在 onReceive 中做耗时操作publicclassMyReceiverextendsBroadcastReceiver{@OverridepublicvoidonReceive(Contextcontext,Intentintent){// 这里超过10秒就会ANRprocessLargeData();// 模拟耗时操作}}4. Service 的 onCreate / onStartCommand 耗时
// ❌ 错误示例:Service启动时初始化过多publicclassMyServiceextendsService{@OverridepublicintonStartCommand(Intentintent,intflags,intstartId){// 超过20秒ANRinitDatabase();// 耗时loadConfig();// 耗时syncAllData();// 耗时returnSTART_STICKY;}}5. 主线程同步等待子线程结果
// ❌ 错误示例:主线程阻塞等待子线程publicvoidonClick(Viewv){finalString[]result=newString[1];Threadthread=newThread(()->{result[0]=doHeavyWork();});thread.start();thread.join();// 主线程阻塞等待!如果子线程超过5秒,ANRtextView.setText(result[0]);}6. 过度绘制或布局层级过深(间接导致)
虽然严格来说不是直接阻塞主线程,但过于复杂的布局会导致onMeasure/onLayout/onDraw耗时过长,在低端设备上可能触发输入事件超时。
四、ANR 的检测与诊断
1. 查看 traces.txt
ANR 发生时,系统会生成/data/anr/traces.txt,包含所有线程的堆栈信息。
adb pull /data/anr/traces.txt关键信息:
main线程的状态(RUNNABLE、BLOCKED、WAITING)- 阻塞在哪个方法、哪行代码
- 锁的持有者和等待者
2. 使用 StrictMode
// 开发阶段检测主线程违规操作publicclassMyAppextendsApplication{@OverridepublicvoidonCreate(){super.onCreate();StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog()// 只打印日志,不上报.build());}}3. 使用 Systrace / Perfetto
抓取系统级 trace,分析主线程在每个阶段的耗时。
五、如何预防 ANR
1. 耗时操作放到子线程(核心原则)
// ✅ 正确示例:使用 Thread + HandlerpublicvoidonClick(Viewv){newThread(()->{finalStringresult=HttpUtils.get("https://api.example.com/data");runOnUiThread(()->textView.setText(result));}).start();}2. 使用 AsyncTask(已废弃,了解即可)
// AsyncTask 在 Android 11 已废弃,建议用协程或线程池替代3. 使用 Kotlin 协程(推荐)
// ✅ 推荐:Kotlin 协程lifecycleScope.launch{valresult=withContext(Dispatchers.IO){// 在 IO 线程执行耗时操作repository.fetchData()}// 自动切回主线程更新 UItextView.text=result}4. 使用线程池
// ✅ 正确示例:使用线程池ExecutorServiceexecutor=Executors.newFixedThreadPool(4);publicvoidonClick(Viewv){executor.execute(()->{finalStringresult=doHeavyWork();runOnUiThread(()->textView.setText(result));});}5. BroadcastReceiver 中使用 goAsync()
// ✅ 正确示例:异步处理广播publicclassMyReceiverextendsBroadcastReceiver{@OverridepublicvoidonReceive(Contextcontext,Intentintent){finalPendingResultpendingResult=goAsync();newThread(()->{try{processLargeData();}finally{pendingResult.finish();// 必须调用,否则系统会ANR}}).start();}}6. Service 中使用 IntentService / WorkManager
// ✅ 正确示例:IntentService(已废弃,建议用 WorkManager)// 或使用 JobIntentService / WorkManager 处理后台任务7. 避免主线程同步锁
// ✅ 正确示例:使用回调代替阻塞等待publicvoidonClick(Viewv){fetchDataAsync(newCallback<String>(){@OverridepublicvoidonResult(Stringresult){runOnUiThread(()->textView.setText(result));}});}8. 优化布局性能
- 减少布局层级(使用 ConstraintLayout)
- 避免过度绘制
- 使用
ViewStub延迟加载 - 避免在
onDraw中创建对象
六、总结:ANR 预防 checklist
| 场景 | 预防措施 |
|---|---|
| 网络请求 | 必须用子线程/协程 |
| 数据库操作 | 用 Room + 协程,或子线程 |
| 文件 I/O | 子线程,SharedPreferences 用apply()代替commit() |
| 复杂计算 | 子线程,必要时用AsyncTask替代品 |
| 同步等待 | 用回调、Future、协程替代join()/wait() |
| 广播接收 | 用goAsync()或启动 Service 处理 |
| Service 启动 | 避免在onCreate中做耗时初始化 |
| 布局优化 | 减少层级,避免过度绘制 |
一句话总结:ANR 的本质是主线程被阻塞,预防的核心就是不要让主线程干重活——耗时操作全部异步化,同步依赖全部回调化。
