Android 中 ANR 的主要原因及消除方法(完整开发者指南)

发布: (2026年4月7日 GMT+8 02:33)
6 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的具体文本内容,我将按照要求将其翻译成简体中文并保留原有的 Markdown 格式。谢谢!

介绍

想象一下,用户打开你的 Android 应用,点击一个按钮,却没有任何反应。屏幕卡住了。几秒钟后,Android 显示 Application Not Responding (ANR)。大多数用户不会等待——他们会关闭应用并卸载它。这就是为什么 ANR 是 Android 应用中最致命的性能问题之一。

根据 Android Developers 文档,ANR 发生在主线程被阻塞,无法在规定时间内响应用户输入时。如果你的 ANR 率升高,Google Play 可能会降低你的应用可见度。因此,了解 ANR 至关重要。

什么是 ANR?(简明解释)

官方定义

ANR 发生在 UI 线程被阻塞,无法处理用户输入或绘制帧时。Android 在以下情况下显示 ANR:

  • 输入事件在 5 秒 内未处理
  • BroadcastReceiver 运行时间过长
  • 服务启动耗时过长
  • 作业或前台服务响应延迟

实际示例

把你的应用想象成一家餐厅。

角色类比
UI 线程服务员
后台线程厨房
用户顾客

正常流程
顾客点单 → 服务员把单子送到厨房 → 厨房准备 → 服务员送餐(顺畅)。

异常流程(ANR)
顾客点单 → 服务员自己去厨房开始烹饪 → 没有人接新单,也没人上菜 → 餐厅停止运作。
这对应于在 UI 线程上进行大量工作。

ANR的主要原因

1. 主线程上的重工作

在 UI 线程上运行网络请求、数据库查询、文件读取、JSON 解析或图像处理会阻塞它。Android 文档指出:保持主线程不被阻塞,将耗时工作移到工作线程。

2. 长时间的数据库或网络操作

val data = api.getUsers() // running on main thread

这会阻塞 UI,可能导致 ANR。

3. BroadcastReceiver 执行重工作

BroadcastReceiver 应只执行快速任务。长时间的工作放在 onReceive 中会导致 ANR。Android 建议将此类工作移到后台线程。

4. 服务未及时启动

前台服务必须在 5 秒 内调用 startForeground();否则会出现 ANR。

5. 死锁和线程阻塞

当一个线程等待另一个线程(例如线程 A 等待线程 B)时,应用会卡死。死锁是导致 ANR 的主要原因。

常见开发者错误

在 Activity 中运行 API 调用

override fun onCreate() {
    super.onCreate()
    val users = api.getUsers() // 错误:在 UI 线程上运行
}

在 UI 线程上进行大规模 JSON 解析

val json = File("data.json").readText() // 错误:阻塞 UI

避免 ANR 的最佳实践

  • 使用协程

    viewModelScope.launch {
        val users = repository.getUsers()
    }
  • 对 I/O 操作使用 Dispatchers.IO

    withContext(Dispatchers.IO) {
        api.getUsers()
    }
  • 使用 Flow 处理连续数据

    repository.getUsersFlow()
        .flowOn(Dispatchers.IO)
  • 使用 WorkManager 进行后台任务(例如,同步、上传、下载、调度)

  • BroadcastReceiver 保持轻量

    override fun onReceive(context: Context, intent: Intent) {
        CoroutineScope(Dispatchers.IO).launch {
            repository.sync()
        }
    }

现代 Kotlin 代码示例

class UserRepository(
    private val api: UserApi
) {
    suspend fun getUsers(): List = withContext(Dispatchers.IO) {
        api.getUsers()
    }
}
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {

    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users = _users.asStateFlow()

    init { loadUsers() }

    private fun loadUsers() {
        viewModelScope.launch {
            _users.value = repository.getUsers()
        }
    }
}
@Composable
fun UserScreen(viewModel: UserViewModel) {
    val users by viewModel.users.collectAsState()
    LazyColumn {
        items(users) { user ->
            Text(user.name)
        }
    }
}

实际案例

场景: 应用扫描设备视频。

  • 错误:Activity 中直接扫描文件 → UI 卡死 → ANR。
  • 正确: 显示启动屏,让仓库使用 Dispatchers.IO 进行扫描,通过 Flow 发射结果,并在数据到达时更新 UI。

结果: 没有 ANR,用户体验流畅。

关键要点

  • 核心规则: 永远不要阻塞主线程。
  • UI 线程必须始终保持空闲,以便用户输入和渲染。

结论

ANR 不仅是性能问题;它们直接影响用户体验、应用评分、Play 商店排名和收入。最安全的策略很简单:保持 UI 线程干净,将所有繁重的工作移到后台线程。通过遵循 Android 官方指南和现代 Kotlin 实践,几乎可以消除 ANR。

0 浏览
Back to Blog

相关文章

阅读更多 »