Android 慢性病之拒绝"带病"上线:为什么 ANR 是必须根除的代码 HP?

摘要: ANR 从来不是突然出现的意外,而是主线程长期带病运行的结果。本文从"幽门螺杆菌"的病程演化切入,深度拆解 ANR 的四阶段恶化路径,并给出一套架构级的根治方案------Data 层四条红线。不靠优化续命,而是靠隔离断根。

一、写在前面:一个被忽视的"危险信号"

这两年,身边很多中年同事,体检报告里开始出现"幽门螺杆菌(HP)阳性。

一开始大家的反应几乎一模一样:

  • "没事吧,好像很多人都有?"
  • "医生也没让我立刻治。"
  • "先放着吧,最近太忙了。"

听起来是不是很熟?


但真正危险的,从来不是"有没有 HP",而是这种心态:

"反正现在没症状,先不管。"


HP 的问题就在这里:

  • 不会立刻让你疼
  • 不会影响你今天的工作
  • 甚至几年内都"看起来没事"

但它会在你看不见的地方:

  • 持续破坏胃黏膜
  • 改变胃部环境
  • 一点一点降低你的"健康冗余"

直到某一天:

👉 胃炎 👉 胃溃疡 👉 更严重的问题

你才意识到:

问题从来不是"突然出现",而是"长期放任"。


然后我意识到一件事:

我们对 ANR 的态度,和对 HP 的态度,一模一样。


在代码里,我们每天都在做类似的选择:

  • "这个 IO 放主线程问题不大吧?"
  • "这个接口先用同步写,后面再优化。"
  • "这个卡顿用户应该感觉不到。"

这些代码,就像 HP:

  • 不会立刻出问题
  • 不影响你今天上线
  • 甚至还能稳定运行一段时间

但它们会:

  • 悄悄污染主线程
  • 慢慢堆积消息队列
  • 降低系统调度能力

直到某一天:

ANR

突然出现在用户面前。


ANR 从来不是一次事故,而是一种"长期带病运行"的结果。


二、病程全解析:ANR 的四阶段演化

1️⃣ 潜伏期:无感阻塞

特征:

  • 高端机正常
  • Debug 无感
  • 测试看不出来

👉 主线程开始被污染


2️⃣ 诱发期:环境放大

场景 后果
低端机 IO 放大
CPU 忙 排队
GC 抖动
磁盘忙 阻塞

👉 10ms → 500ms


3️⃣ 萎缩期:掉帧(Jank)

👉 用户开始"感觉不对劲"


4️⃣ 爆发期:ANR

👉 系统强制弹窗 👉 用户唯一操作:关闭


三、真正的转折点:HP 为什么能"根治"?

很多人知道 HP 可以治。

但关键不是吃药。

而是:

必须"分餐制",否则会反复感染。


👉 药物解决"当前问题" 👉 分餐解决"系统性传播"


这件事非常关键,因为它对应到工程里就是:

你不做架构隔离,ANR 永远会复发。


四、ANR 的"工程化根治":不是优化,而是"隔离"

很多文章会讲:

  • StrictMode
  • 协程
  • Flow

这些都没错。

但本质上它们只是:

对症治疗


真正的"分餐制",是这一层:


五、核心升级:Data 层四条红线(真正的根治)

⚠️ 这不是建议,是底线。


🚫 红线一:接口必须统一为 Flow / suspend

🚫 红线二:禁止 BlockingQueue

本质问题:线程阻塞污染协程调度


🚫 红线三:禁止 Java 重锁

本质问题:阻塞线程,破坏调度


🚫 红线四:禁止伪 suspend

本质问题:协程壳 + 阻塞内核


总结一句话:
Data 层必须彻底协程化,否则主线程迟早出问题。


六、更深一层:为什么要"禁止 AQS"?

在协程成为主流之后,我越来越坚定一个观点:

业务层不要显式使用 AQS 同步器。

包括:

  • ReentrantLock
  • CountDownLatch
  • Semaphore
  • FutureTask
  • synchronized

1️⃣ 问题不在 API,而在"层级错位"

AQS 解决的是:

  • 线程竞争
  • 线程阻塞

而协程解决的是:

  • 任务调度
  • 挂起恢复

当你在协程里用 ReentrantLock:

本质是在用线程模型解决任务问题


2️⃣ 阻塞 vs 挂起

机制 行为
ReentrantLock 阻塞线程
Mutex 挂起协程

👉 最大区别:

是否占用线程


错误后果:

  • 线程池耗尽
  • 调度抖动
  • 假死
  • 难排查

3️⃣ 正确并发模型

业务层应该用:

  • Mutex
  • Channel
  • Flow
  • StateFlow
  • async/await

更进一步:

  • actor 模型
  • 单线程 dispatcher
  • 状态建模

4️⃣ AQS 只属于底层

只允许两种场景:

  1. 写基础库
  2. 兼容旧 Java 系统

AQS 是地基,业务代码不应该住在地基里。

总结

HP 可以靠吃药缓解,但不分餐一定复发。ANR 可以靠优化缓解,但不做架构隔离,一定回归。不做隔离的优化,本质上都是"延迟复发"。

参考

并发编程的新篇章:以Kotlin协程告别JUC的重锁与死锁风险

为什么 Java 的锁锁不住 Kotlin 协程?

Repository 方法设计:suspend 与 Flow 的决选择指南(以朋友圈为例)

Android Data 层设计的四条红线:为什么必须坚持、如何落地

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui