拒绝背诵!一文带你打穿 Android ANR 发生的底层全链路

1. 背景引入

在 Android 的世界里,UI 线程(Main Thread)就是 App 的灵魂。如果灵魂被"锁住"了,用户疯狂点击屏幕却得不到回应,那等待你的就是系统的终极审判------ANR (Application Not Responding)

很多同学以为 ANR 就是主线程阻塞了,其实这只是表现。ANR 的本质是:App 没有在规定时间内,给系统(SystemServer)回个信儿。

2. ANR 的底层全链路:埋雷与拆弹

Android 系统为了保证流畅度,给四大组件和输入事件都设置了"定时炸弹"。

2.1 埋雷机制(Set Timeout)

当一个 Service 要启动或者一个 KeyDown 事件要分发时,SystemServer(具体是 AMS 或 InputManagerService)会通过 Binder 调用 App 进程。在调用的同时,系统会往自己的 Handler 里发一个延迟消息

形象比喻:系统就像一个严厉的班主任,他给你发了一张卷子(任务),同时在大屏幕上开启了一个倒计时闹钟。如果闹钟响了你还没交卷,他就直接判定你不及格(ANR)。

2.2 核心阈值表

触发场景 前台时长 后台时长 关键类
Input Event 5s N/A InputDispatcher
Broadcast 10s 60s BroadcastQueue
Service 20s 200s ActiveServices
ContentProvider 10s N/A ActivityManagerService

3. 源码级解析:以 Service 为例

我们来看一段简化后的 ActiveServices.java 源码逻辑(伪代码):

Kotlin 复制代码
// SystemServer 进程中的逻辑
fun realStartServiceLocked(r: ServiceRecord) {
    // 1. 埋雷:发送延迟消息
    bumpServiceExecutingLocked(r, "start")
    
    // 2. 跨进程调用 App 进程执行 Service
    app.thread.scheduleCreateService(r)
}

fun bumpServiceExecutingLocked(r: ServiceRecord, why: String) {
    // ... 发送一个延迟消息 SERVICE_TIMEOUT_MSG
    mAm.mHandler.sendMessageDelayed(msg, r.isForeground ? 20000 : 200000)
}

如果在 20 秒内,App 进程执行完了 onCreate() 并回调了 serviceDoneExecuting,那么系统会:

Kotlin 复制代码
fun serviceDoneExecutingLocked(...) {
    // 3. 拆弹:移除延迟消息
    mAm.mHandler.removeMessages(SERVICE_TIMEOUT_MSG, r)
}

如果主线程卡住了(比如有个死循环或 Deadlock),removeMessages 就永远不会被执行,炸弹爆炸!

4. 实战模拟:手动制造一个 ANR

在开发中,最常见的套路就是在 onCreate 里做耗时操作。

Kotlin 复制代码
class MyService : Service() {
    override fun onCreate() {
        super.onCreate()
        // 模拟耗时操作:比如读取大文件或同步网络请求
        // 这会导致主线程无法处理后续的"拆弹"指令
        Thread.sleep(30000) 
    }
}

5. 避坑指南:如何精准定位?

当 ANR 发生时,系统会生成一个 /data/anr/traces.txt 文件。作为架构师,你必须学会看它。

  • Look for "main" thread:看主线程在干嘛。
  • State: BLOCKED / WAITING:通常是拿不到锁。
  • CPU usage:如果 CPU 满负荷,可能是计算密集型任务;如果 CPU 闲置,大概率是死锁或 IO 阻塞。

性能优化小 Tip:

不要过度依赖 Thread.sleep 或者是复杂的同步锁。优先使用 Kotlin Coroutines (协程) 来处理异步任务。

Kotlin 复制代码
// 推荐写法
lifecycleScope.launch(Dispatchers.IO) {
    val data = fetchData() // 异步读取数据
    withContext(Dispatchers.Main) {
        updateUI(data) // 切回主线程更新
    }
}

复盘总结

ANR 不是洪水猛兽,它是系统对用户体验的最后一道防线。理解了 "埋雷-拆弹" 机制,你就能从系统层面上预防它的发生。

核心要点:

  • ANR 是系统服务端和 App 端的跨进程"博弈"。
  • 主线程阻塞不仅仅是因为代码慢,更因为耗时操作挤压了 Handler 消息队列。
  • 善用 StrictModeLeakCanary 等工具,在开发阶段消灭隐患。

各位掘友,你们在项目中遇到过最离谱的 ANR 是什么原因导致的?是数据库大事务还是第三方 SDK 的锅?欢迎在评论区留言,我们一起在坑里跳个舞!

相关推荐
又见情义14 分钟前
RK3568 Android 13开机时间优化-系统应用裁剪实践
android
hunterandroid1 小时前
Android 内存泄漏排查实战:从 LeakCanary 报警到根因定位
android·前端
RisunJan1 小时前
产品经理面试知识点梳理
面试·职场和发展·产品经理
我命由我123451 小时前
Android 设备的日志缓冲区被写满,logcat: Unexpected EOF!
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
171320330计算机毕设编程2 小时前
基于SpringBoot的在线拍卖系统
android·java·spring boot·小程序·课程设计
用户0934077735142 小时前
HarmonyOS WPS Open SDK 实践:OpenFileRequest 打开链路与沙箱拷贝
android·typescript·harmonyos
0xBADCODE2 小时前
动态DEX加载+反射+DES硬编码密钥:安卓三层逆向实战
android·java·python·安全·网络安全·逆向·ctf
Patrick在香港2 小时前
Python 拉取 C&SD 官方 API:香港 2022 年已跨过“超老龄线“,而抚养比正在爬回 1961
android·c语言·python·数据分析·时序数据库·数据可视化·香港
mmsx3 小时前
osmdroid 地图实战 03|谷歌影像的 URL,为什么不能 setTileSource 了事?
android
CoderYanger3 小时前
A.每日一题:3345. 最小可整除数位乘积 I
java·数据结构·程序人生·算法·leetcode·面试·学习方法