Android随笔-ANR

📅 2026/7/17 1:15:32
Android随笔-ANR
一、ANR 是什么ANRApplication Not Responding应用无响应是 Android 系统的一种自我保护机制当主线程UI Thread在规定时间内无法响应用户输入或系统事件时系统会弹出应用无响应对话框提示用户选择等待或关闭应用。本质上ANR 是主线程被阻塞导致消息队列MessageQueue中的事件无法被及时处理。二、ANR 的判定标准超时阈值场景超时时间触发条件输入事件 ANR5 秒按键、触摸等输入事件未被处理Broadcast ANR10 秒前台/ 60 秒后台onReceive()执行超时Service ANR20 秒onCreate()/onStartCommand()执行超时ContentProvider ANR10 秒onCreate()执行超时注意这些超时时间是硬编码在系统源码中的应用层无法修改。三、哪些场景会产生 ANR1. 主线程执行耗时操作最常见// ❌ 错误示例主线程直接网络请求OverridepublicvoidonClick(Viewv){// 阻塞主线程超过5秒就会ANRStringresultHttpUtils.get(https://api.example.com/data);textView.setText(result);}其他常见耗时操作网络请求HTTP、Socket数据库读写SQLite文件 I/O读写大文件、SharedPreferences 的commit()大量数据计算复杂算法、图片编解码sleep()或wait()长时间阻塞2. 主线程死锁// ❌ 错误示例主线程死锁privatefinalObjectlockAnewObject();privatefinalObjectlockBnewObject();// 线程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[]resultnewString[1];ThreadthreadnewThread(()-{result[0]doHeavyWork();});thread.start();thread.join();// 主线程阻塞等待如果子线程超过5秒ANRtextView.setText(result[0]);}6. 过度绘制或布局层级过深间接导致虽然严格来说不是直接阻塞主线程但过于复杂的布局会导致onMeasure/onLayout/onDraw耗时过长在低端设备上可能触发输入事件超时。四、ANR 的检测与诊断1. 查看 traces.txtANR 发生时系统会生成/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分析主线程在每个阶段的耗时。五、如何预防 ANR1. 耗时操作放到子线程核心原则// ✅ 正确示例使用 Thread HandlerpublicvoidonClick(Viewv){newThread(()-{finalStringresultHttpUtils.get(https://api.example.com/data);runOnUiThread(()-textView.setText(result));}).start();}2. 使用 AsyncTask已废弃了解即可// AsyncTask 在 Android 11 已废弃建议用协程或线程池替代3. 使用 Kotlin 协程推荐// ✅ 推荐Kotlin 协程lifecycleScope.launch{valresultwithContext(Dispatchers.IO){// 在 IO 线程执行耗时操作repository.fetchData()}// 自动切回主线程更新 UItextView.textresult}4. 使用线程池// ✅ 正确示例使用线程池ExecutorServiceexecutorExecutors.newFixedThreadPool(4);publicvoidonClick(Viewv){executor.execute(()-{finalStringresultdoHeavyWork();runOnUiThread(()-textView.setText(result));});}5. BroadcastReceiver 中使用 goAsync()// ✅ 正确示例异步处理广播publicclassMyReceiverextendsBroadcastReceiver{OverridepublicvoidonReceive(Contextcontext,Intentintent){finalPendingResultpendingResultgoAsync();newThread(()-{try{processLargeData();}finally{pendingResult.finish();// 必须调用否则系统会ANR}}).start();}}6. Service 中使用 IntentService / WorkManager// ✅ 正确示例IntentService已废弃建议用 WorkManager// 或使用 JobIntentService / WorkManager 处理后台任务7. 避免主线程同步锁// ✅ 正确示例使用回调代替阻塞等待publicvoidonClick(Viewv){fetchDataAsync(newCallbackString(){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 的本质是主线程被阻塞预防的核心就是不要让主线程干重活——耗时操作全部异步化同步依赖全部回调化。