ANR(Application Not Responding),即“应用无响应”,是 Android 系统中一种用于判断应用主线程(UI 线程)是否阻塞的机制。当用户输入事件或系统关键消息在规定时间内未得到处理时,系统会判断应用发生了 ANR,并向用户弹出强制关闭应用的对话框。
这是一种糟糕的用户体验,直接影响应用留存率和口碑,因此理解和解决 ANR 是每个 Android 开发者的必备技能。
系统触发 ANR 主要有以下几种场景,超时阈值可能因 Android 版本而异:
5
秒内未能响应用户的输入事件(如按键、触摸)。onReceive()
方法在规定时间内未能执行完毕。
Intent type | Android 13 及更低版本 | Android 14 及更高版本 |
---|---|---|
前台优先级 intent (已设置 FLAG_RECEIVER_FOREGROUND ) |
10 秒 | 10-20 秒,具体取决于进程是否已耗尽 CPU |
后台优先级 intent (未设置 FLAG_RECEIVER_FOREGROUND ) |
60 秒 | 60-120 秒,具体取决于进程是否已耗尽 CPU |
20
秒内未能执行完其生命周期方法(如 onCreate()
, onStartCommand()
)。200
秒内未能执行完其生命周期方法。ContentProviderClient.setDetectNotResponding
指定。ANR 超时期限包含远程 content provider 查询运行的总时间,其中包括对尚未运行的远程应用进行冷启动的时间。所有 ANR 的根源都可以归结为一点:在主线程中执行了耗时操作。这导致主线程被阻塞,无法及时处理系统和用户的消息。
具体来说,常见原因包括:
SharedPreferences
(apply()
是异步的,但 commit()
是同步的)。主线程尝试获取一个由其他线程(通常是工作线程)持有的锁,但该工作线程由于某种原因(如等待主线程的资源)而无法释放锁,导致主线程无限期等待,最终引发 ANR。
BroadcastReceiver
的 onReceive()
或 Service
的生命周期方法本身逻辑过于复杂,或在其中启动了其他耗时任务,超出了系统规定的时间限制。
当 ANR 发生时,系统会生成一份详细的诊断信息,这是我们定位问题的关键。
在开发和调试阶段,Logcat
是最直接的工具。当 ANR 发生时,你会在 Logcat 中看到类似以下的日志:
E/ActivityManager: ANR in com.example.myapp (com.example.myapp/.MainActivity)
E/ActivityManager: PID: 12345
E/ActivityManager: Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 5000.0ms ago)
这条日志明确指出了发生 ANR 的包名、进程 ID (PID) 和原因。
这是定位 ANR 的核心武器。当 ANR 发生时,系统会将当时所有线程的堆栈信息快照保存到 /data/anr/traces.txt
文件中。
traces.txt
?adb pull /data/anr/traces.txt
# 生成包含 ANR 信息的完整 bug 报告
adb bugreport bugreport.zip
解压 bugreport.zip
后,在其中查找 anr
相关的文件。traces.txt
?traces.txt
文件包含了案发现场的所有线程状态,分析时重点关注以下几点:
找到主线程:主线程的名称通常是 main
。它的堆栈信息是分析的重中之重。
"main" prio=5 tid=1 NATIVE
| group="main" sCount=1 dsCount=0 flags=1 obj=0x72a6b3d0 self=0x7c80a2b400
| sysTid=12345 nice=-10 cgrp=top-app sched=0/0 handle=0x7d0139a9c8
| state=S schedstat=( 123456789 987654321 1234 ) utm=8 stm=4 core=7 HZ=100
| stack=0x7fd7f8a000-0x7fd7f8c000 stackSize=8MB
| held mutexes=
at com.example.myapp.MyHeavyWork.doSomethingCPUIntensive(MyHeavyWork.java:42)
at com.example.myapp.MainActivity$1.onClick(MainActivity.java:101)
... (more stack trace)
分析主线程堆栈:
检查线程状态:
WAITING
/ BLOCKED
:如果主线程状态是 WAITING
或 BLOCKED
,说明它正在等待某个锁或条件。此时需要找到持有该锁的线程,分析其为何长时间不释放锁,是否存在死锁。RUNNING
:如果主线程是 RUNNING
状态,说明它正在执行一个耗时的计算任务。定位问题后,修复 ANR 的核心原则就是:确保主线程的轻量与流畅。
这是解决绝大多数 ANR 问题的根本方法。推荐使用 Kotlin 协程(Coroutines)进行异步处理。
优化前(错误示例)
// 在 Activity 或 Fragment 中
fun fetchData() {
// 错误: 在主线程执行网络请求,极易导致 ANR
val result = apiService.fetchDataSync()
updateUi(result)
}
优化后(正确示例)
// 在 ViewModel 中使用 viewModelScope
// 引入依赖: implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2"
class MyViewModel(private val apiService: ApiService) : ViewModel() {
private val _uiState = MutableLiveData<UiState>()
val uiState: LiveData<UiState> = _uiState
fun fetchData() {
// 启动一个协程,不阻塞主线程
viewModelScope.launch {
try {
_uiState.value = UiState.Loading
// 使用 withContext 将网络请求切换到 IO 线程池
val result = withContext(Dispatchers.IO) {
apiService.fetchDataSync() // 耗时操作在这里执行
}
// 结果自动切回主线程更新 UI
_uiState.value = UiState.Success(result)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
}
}
viewModelScope
:提供了与 ViewModel
生命周期绑定的协程作用域,当 ViewModel
销毁时自动取消协程。Dispatchers.IO
:专为 I/O 密集型任务设计的线程池。withContext
:在不阻塞当前协程的情况下,将代码块切换到指定的调度器执行,执行完毕后自动切回原调度器。synchronized
代码块应只包裹必要的、最小化的临界区代码。tryLock
替代无限等待的 lock
。java.util.concurrent
包下的线程安全集合,如 ConcurrentHashMap
,它们通常比使用 synchronized
包装的普通集合有更好的性能。onReceive()
方法必须在短时间内返回。任何耗时操作都应被代理出去。
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// goAsync() 使得广播接收器在 onReceive 返回后仍保持活跃状态
val pendingResult: PendingResult = goAsync()
// 在后台线程中执行耗时任务
GlobalScope.launch(Dispatchers.IO) {
try {
// 执行你的耗时操作,例如:
performLongRunningTask(context, intent)
} finally {
// 任务完成后,必须调用 finish()
pendingResult.finish()
}
}
}
private fun performLongRunningTask(context: Context, intent: Intent) {
// ... 这里是耗时逻辑 ...
// 更好的实践是使用 WorkManager 来调度这个任务
}
}
最佳实践:对于可延迟的后台任务,强烈建议使用
WorkManager
。它能保证任务在合适的时机(如设备充电、连接 Wi-Fi 时)被执行,并能在应用退出或设备重启后继续执行。
与其被动修复,不如主动预防。
在应用的 Debug 版本中开启 StrictMode
,它是一个强大的开发工具,可以检测出主线程中的违规操作(如磁盘 I/O、网络请求),并通过日志或崩溃报告给你。
在 Application
的 onCreate
方法中添加:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog() // 在 Logcat 中打印违规日志
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
)
}
}
}
建立严格的代码审查流程,重点关注主线程中的方法调用,确保没有潜在的耗时操作被合入。
定期使用 Android Studio Profiler 的 CPU Profiler 分析应用性能,主动发现并优化主线程中的性能瓶颈。
ANR 是衡量应用质量和用户体验的关键指标。解决 ANR 的过程可以概括为以下步骤:
Logcat
和 traces.txt
分析工具,找到阻塞的源头。StrictMode
和性能分析工具,将 ANR 风险扼杀在摇篮里。保持主线程的畅通无阻,是打造高质量、高性能 Android 应用的基石。