Android ANR 排查指南(思路、方法与实战案例)

Android ANR 排查指南:思路、方法与实战案例

核心 :解决 ANR 不是「看到堆栈就改代码」,而是先回答三个问题------谁阻塞了主线程 、阻塞了多久 、为什么没及时返回 。
本质:系统要求某个线程/组件在规定时间内响应,但它没有完成。


先建立一条最短排查路径

如果是第一次系统地排查 ANR,不必急着把整份文档从头到尾读完。先把下面 5 步记住,后面的内容再逐段展开:

  1. 先确认 Reason / ANR 类型
  2. 立刻保留现场 :logcat、traces.txt、bugreport
  3. 先看 main 栈顶 10~20 行
  4. 如果 main 在等,就顺着追责任线程或对端
  5. 责任链确认后,再回到代码修复

一句话先记住:

谁让 main 等,谁就是重点。


先统一几个常见术语

初次接触 ANR 时,最容易被术语绊住。下面这张表只追求先建立直觉,不是严格下定义:

术语 先这样理解
ANR App 或系统组件在规定时间内没响应,系统把它判成"无响应"
main 线程 UI 主线程;页面展示、点击响应、很多生命周期都依赖它
traces.txt 某一时刻所有线程"正在做什么"的快照
Reason: 系统给你的第一条线索,先用它判断 ANR 大类
Binder 进程间调用通道;可先理解成"我问另一个进程要结果"
system_server Android 里很多系统服务所在的核心进程
HAL / vendor 更靠近硬件和厂商实现的一层,常见于相机、音视频等链路
held by thread 48 这把锁现在在 48 号线程手里
Blocked / Waiting / Runnable 常见线程状态;别只看状态名,要结合栈顶方法一起判断

阅读建议

区块 用途
第一部分 类型速查、一张主流程图、根因脑图;先建立整体判断框架,再回看具体现场
第二部分 按真实排查顺序展开:从确认现场、阅读主线程、追责任线程,到修复与验证,适合在手里有问题时直接对照
第三部分 拿到 traces.txt 后:如何逐段读线程块、判断锁等待 / Binder / 死锁并顺藤摸瓜
第四部分 真实风格案例:从普通 App 场景到相机链路场景,按「怎么看 → 结论 → 修复」完整展开
附录 命令合集、修复优先级表、反面代码示例

可以按下面的顺序阅读

  • 第一次系统读 ANR:先看先建立一条最短排查路径 → [清单 1~4](#清单 1~4) → [普通 App 场景案例](#普通 App 场景案例)。
  • 手里已经有 traces.txt:直接看第三部分 的"固定套路"和第四部分案例。
  • 已经有一些排查经验:把第二部分当排查清单,把第三部分和第四部分当作查阅与对照材料。

目录

开始前

  1. 先建立一条最短排查路径
  2. 先统一几个常见术语
  3. 阅读建议

第一部分 · 思维与参考

  1. [ANR 类型速查](#ANR 类型速查)
  2. [ANR 排查主流程(整合流程图)](#ANR 排查主流程(整合流程图))
  3. 常见根因(Mindmap)
  4. 主线程「看起来正常」时

第二部分 · 实战排查清单

  1. [清单 1 --- 先确认 ANR 基本信息](#清单 1 — 先确认 ANR 基本信息)
  2. [清单 2 --- 第一优先级:拿现场资料](#清单 2 — 第一优先级:拿现场资料)
  3. [清单 3 --- 先看主线程,不要先看业务代码](#清单 3 — 先看主线程,不要先看业务代码)
  4. [清单 4 --- 主线程卡住后,继续追到责任线程](#清单 4 — 主线程卡住后,继续追到责任线程)
  5. [清单 5 --- 按 ANR 类型继续缩小范围](#清单 5 — 按 ANR 类型继续缩小范围)
  6. [清单 6 --- 线程与锁专项检查](#清单 6 — 线程与锁专项检查)
  7. [清单 7 --- Binder / 系统服务专项检查](#清单 7 — Binder / 系统服务专项检查)
  8. [清单 8 --- 启动场景专项检查](#清单 8 — 启动场景专项检查)
  9. [清单 9 --- UI 卡顿导致 ANR 的专项检查](#清单 9 — UI 卡顿导致 ANR 的专项检查)
  10. [清单 10 --- CPU / 内存 / GC / IO 环境检查](#清单 10 — CPU / 内存 / GC / IO 环境检查)
  11. [清单 11 --- 日志排查](#清单 11 — 日志排查)
  12. [清单 12 --- Perfetto / Systrace](#清单 12 — Perfetto / Systrace)
  13. [清单 13 --- 修复方案](#清单 13 — 修复方案)
  14. [清单 14 --- 验证](#清单 14 — 验证)
  15. [清单 15 --- 复盘时必须回答的 5 个问题](#清单 15 — 复盘时必须回答的 5 个问题)
  16. [清单 16 --- 团队 ANR 分析模板](#清单 16 — 团队 ANR 分析模板)
  17. [清单 17 --- 排查口诀](#清单 17 — 排查口诀)

第三部分 · 逐行阅读 traces.txt

  1. 总览:目标与阅读顺序
  2. 线程块:五个关注点
  3. [第一步:定位进程与 main](#第一步:定位进程与 main)
  4. 锁等待
  5. [Object.wait / await / get / join](#Object.wait / await / get / join)
  6. [Binder 阻塞](#Binder 阻塞)
  7. 死锁
  8. 四步读法套路
  9. [完整示例:锁 / Binder / 死锁](#完整示例:锁 / Binder / 死锁)
  10. 易误判
  11. [traces 填写模板与速查表](#traces 填写模板与速查表)

第四部分 · traces 真实案例拆解

  1. 总览
  2. [案例一:普通 App 点击后同步查库](#案例一:普通 App 点击后同步查库)
  3. 案例二:主线程抢锁
  4. [案例三:openCamera() Binder 阻塞](#案例三:openCamera() Binder 阻塞)
  5. [案例四:Future.get() 伪异步](#案例四:Future.get() 伪异步)
  6. 四类快速区分
  7. 固定读法五步走
  8. 最后用两句话收束

附录

  1. [附录 A --- 常用命令与工具](#附录 A — 常用命令与工具)
  2. [附录 B --- 修复手段优先级与反面示例](#附录 B — 修复手段优先级与反面示例)

第一部分 · 思维与参考

1. ANR 类型速查

类型 含义
Input dispatching timed out 主线程未及时分发/处理输入;UI 卡死、锁、Binder、重计算等
Broadcast of Intent ... BroadcastReceiver 执行过久
executing service ... Service 生命周期或处理过慢
ContentProvider not responding Provider 初始化或 query/insert 等阻塞
Foreground service did not start in time 前台服务未在时限内启动完成
Job / 系统调度 部分系统场景也会表现为 ANR

logcat / ANR 信息里先看 Reason:,再对照上表。


2. ANR 排查主流程(整合流程图)

ANR 排查看起来分支很多,但真正常用的路径其实很稳定:先确认类型与现场 → 看 main → 按栈分类 → 对症处理 → 复现验证。下面把这条路径收束成一张图。
锁 BLOCKED wait park
Binder transact
IO DB decode commit
UI inflate measure draw
重 CPU JSON 遍历等
nativePollOnce 看似空闲
发现 ANR
logcat: Reason / 确认 ANR 类型
收集现场 traces、log、bugreport
定位进程 看 main 栈顶
main 属于哪一类
持锁线程 死锁 主线程等后台
对端进程与服务 本进程 Binder 池
异步化 缓存 减同步等待
减层级 首屏分阶段 列表 bind
移后台 拆任务 UI 仅刷新
扩查其他线程 系统日志
对症修复
复现并加日志或抓 trace
验证 ANR 与卡顿是否消除

读图顺序 :从左到右一条线走完------先 Reason 与现场,再 main 分类 ;每一支既对应「要查什么」(如持锁线程、Binder 对端),也对应「常见修什么」(异步、减锁、移主线程等);最后复现 + 验证,避免只消 ANR 不消卡顿。


3. 常见根因(Mindmap)

ANR 根因
主线程重活
大计算
JSON解析
图片解码
初始化过重
锁竞争
synchronized
ReentrantLock
CountDownLatch
Future.get
Binder阻塞
系统服务慢
跨进程调用慢
对端死锁
IO慢
文件读写
SharedPreferences commit
SQLite
fsync
UI过重
inflate过深
measure/layout/draw耗时
RecyclerView绑定过重
线程模型错误
主线程等后台
后台又等主线程
死锁
启动链路过重
Application
ContentProvider
首页onCreate
第三方/系统依赖
SDK阻塞
WebView
Camera/Media服务


4. 主线程「看起来正常」时

有时主线程停在 Looper.loop、nativePollOnce,不代表没问题,可能包括:

  • 关键消息迟迟未投递
  • 其他线程持锁,主线程后续无法执行
  • Binder 对端阻塞
  • RenderThread / Binder 线程 / 工作线程异常

继续看 :RenderThread、Binder:*、FinalizerDaemon、业务线程池、DB 线程、相机/GL 线程、HandlerThread 等。


第二部分 · 实战排查清单

这一部分按照日常分析 ANR 时更接近真实的思路来安排:先确认现场,再判断主线程状态,然后继续追到责任线程,最后再回到修复与验证。真正排查时不必机械照背,但大多数问题都可以沿着这条顺序逐步收敛。

清单 1 --- 先确认 ANR 基本信息

建议先确认的信息
  • ANR 时间点
  • 机型 / Android 版本
  • App 版本
  • 是否必现 ;发生场景 (可多选对照)
    • 冷启动 / 切前后台 / 页面跳转
    • 点击拍照、录像 / 切换摄像头
    • 设置页打开 / 广播触发 / Service 启动
  • 是否只在某一项目、某一平台出现(如 MTK 、Qualcomm 、特定 ROM)
先判断 ANR 类型

看 logcat / ANR 信息里的 Reason:,常见类型见[第一部分 · ANR 类型速查](#第一部分 · ANR 类型速查)。


清单 2 --- 第一优先级:拿现场资料

建议优先收集的现场
  • logcat
  • traces.txt / /data/anr/anr_*
  • bugreport(条件允许)
  • 发生前后 10~30 秒日志
  • CPU / 内存 / Binder / 磁盘相关信息(能从 bugreport、dumpsys、trace 里尽量捞全)
常用命令(详见[附录 A](#常用命令(详见附录 A)))
bash 复制代码
adb logcat -d > logcat.txt
adb shell ls /data/anr
adb pull /data/anr/anr_xxx ./anr.txt
adb shell dumpsys activity > activity.txt
adb shell dumpsys input > input.txt
adb shell dumpsys cpuinfo > cpuinfo.txt
如果 user 版本拿不到 /data/anr

至少保住:关键 log 、App 自埋点 、DropBoxManager / bugreport 、复现时的 Perfetto。

已有 traces.txt / bugreport 里的 trace 时,可对照 [第三部分 · 逐行阅读 traces.txt](#第三部分 · 逐行阅读 traces.txt) 做锁 / Binder / 死锁的精读。


清单 3 --- 先看主线程,不要先看业务代码

拿到 traces.txt 之后,最先需要回答的问题其实很简单:ANR 发生的那个瞬间,主线程到底在做什么。判断这件事时,先看 main 线程栈,通常比先翻业务代码更有效。(栈上关键字与顺藤摸瓜的方法见 第三部分。)

A. 锁等待
项目 内容
关键词 BLOCKED、waiting to lock、waiting for monitor entry、Object.wait、LockSupport.park、CountDownLatch.await、FutureTask.get
结论 主线程往往不是「自己慢」,而是在等别人
排查重点 找持锁线程 ;找被 await/get 的任务线程 ;查是否死锁 / 环形等待
B. Binder 阻塞
项目 内容
关键词 BinderProxy.transact、transactNative、ContentResolver、PackageManager、ActivityManager、CameraService、SurfaceFlinger 等
结论 主线程在做跨进程调用 ,需查对端是否卡住
排查重点 能否移到后台线程 ;查 system_server / cameraserver / Provider 等对端日志
C. IO / DB
项目 内容
关键词 read、write、open、fsync、SQLite、SharedPreferences.commit、BitmapFactory.decode 等
结论 主线程做了慢 IO 或大图解码
排查重点 异步化 ;缓存 ;减少同步落盘;commit() → apply()
D. UI 构建 / 绘制过重
项目 内容
关键词 performTraversals、measure、layout、draw、inflate、RecyclerView、ConstraintLayout 等
结论 页面太重 或刷新过频
排查重点 查布局层级 、首屏初始化 、列表 bind 、自定义 View
E. 业务重计算
项目 内容
关键词 大量遍历、JSON 解析、图片处理、排序、配置组装、算法预处理等
结论 主线程做了 CPU 重活
排查重点 后台化 ;分段 ;结果回主线程只更新 UI

清单 4 --- 主线程卡住后,继续追到责任线程

只看 main 往往只能看到表象,真正让 ANR 成立的原因,常常还藏在持锁线程、后台任务线程,或者 Binder 的对端进程里。在 traces.txt 里如何搜 held by thread、跟 Binder 对端、画等待链,见 [第三部分(§3 锁、§5 Binder、§6 死锁、§7~§8 套路与示例)](#第三部分(§3 锁、§5 Binder、§6 死锁、§7~§8 套路与示例))。

若 main 在等锁
  • 查锁对象 ;在 trace 里搜谁持有该锁
  • 看持锁线程当时在干什么:是否在 IO 、是否在 Binder 、是否又在等别的锁
若 main 在等异步任务

例如:Future.get()、CountDownLatch.await()、Semaphore.acquire() 等。

  • 后台任务线程有没有真正跑起来
  • 线程池是否满 、任务是否被串行堵死
  • 后台任务是否反过来等主线程
若 main 在 Binder
  • 对端是哪个进程;对端是否繁忙
  • Binder 线程池 是否耗尽;系统服务是否阻塞

清单 5 --- 按 ANR 类型继续缩小范围

当 main 的状态已经大致判断出来之后,就可以结合 Reason: 再把范围缩小一层。不同类型的 ANR,排查重点并不完全相同。

A. Input dispatching timed out(最常见)

无论业务类型如何,都建议先看这些问题

  • 主线程是否在处理点击/触摸时做重活
  • 是否在 onCreate / onResume / onStart / onClick 里同步做耗时任务
  • 是否主线程在等后台结果
  • 是否主线程在等相机 / DB / Provider / Binder 返回
  • 是否大布局 inflate 、首帧过重
  • 是否频繁 notifyDataSetChanged / requestLayout
  • 是否存在锁竞争

如果是普通 App 场景,还可以继续留意

  • 点击后是否直接在主线程查 Room / SQLite
  • 是否在主线程读 SharedPreferences、文件、JSON 配置
  • 是否在主线程做列表数据排序 / 过滤 / 分组 / 映射
  • 是否把网络结果回调后的数据组装全放在主线程
  • 是否把首屏"先显示一点"做成了"等所有数据准备好再显示"

如果是相机场景,下面这些链路尤其值得关注

  • 切模式是否阻塞 UI 线程
  • open / close camera 是否主线程同步等待
  • session configure 是否串在主线程
  • GLSurfaceView / RenderThread / main 是否互等
  • 拍照前参数整理是否在主线程
  • Provider 读配置是否同步卡住
B. BroadcastReceiver ANR

这类问题的共性是:入口很轻,但在 onReceive() 内部把事情做重了。

  • onReceive() 里是否做了耗时逻辑
  • 是否磁盘 / DB / 网络
  • 是否启动流程过重
  • 是否同步等待第三方 SDK 初始化
  • 是否有 Binder 卡住
  • 是否应改为 WorkManager / 后台线程

原则 :onReceive() 只做轻逻辑,快进快出。

C. Service ANR

很多人会天然把 Service 理解成"后台执行",但 Service 的生命周期回调默认仍然跑在主线程。

  • onCreate / onStartCommand / onBind 是否在主线程做重活
  • 前台服务是否启动不及时
  • Service 启动后是否立刻大 IO / 大计算
  • 是否同步等待某初始化完成
  • 是否误以为 Service 默认异步 (默认仍在主线程)
D. ContentProvider ANR

Provider 的问题往往隐蔽,因为它既可能出现在业务读写阶段,也可能在应用启动前就提前暴露出来。

  • Provider.onCreate() 是否重初始化
  • query / update / insert 是否查库太慢
  • 是否缺索引 、是否大结果集
  • 是否被其他进程频繁调用
  • Provider 是否在 App 启动前初始化,拖慢启动

清单 6 --- 线程与锁专项检查

如果 main 看起来是在"等",锁和线程模型通常就是最需要优先怀疑的两个方向。

  • 主线程是否使用 synchronized(或参与同一把锁)
  • 锁里是否做了 IO
  • 锁里是否做了 Binder
  • 锁里是否回调了 UI
  • 是否多把锁嵌套、锁顺序不一致
  • 后台线程持锁时间是否过长
  • 主线程是否 join / get / await
  • 条件变量是否永远等不到 signal

下面这些写法如果出现在主线程附近,通常需要优先提高警惕

java 复制代码
synchronized (lock) {
    readFile();
    remoteCall();
    doHeavyWork();
}
// 以及主线程上的:
future.get();
latch.await();
thread.join();

清单 7 --- Binder / 系统服务专项检查

当主线程的等待指向跨进程调用时,排查思路就不能只停留在当前进程内部。

  • 主线程是否频繁调用系统服务
  • 是否多次小 Binder 串行执行
  • 能否批量 取数、是否有缓存未用
  • 对端进程 是否卡住;Binder 线程池是否满
  • 跨进程 Provider 查询是否过慢
  • 某个系统服务本身是否异常

在相机链路里尤其值得重点关注

  • camera open / close
  • createCaptureSession
  • Surface 配置
  • provider manager / service manager 查询
  • 媒体服务 / cameraserver / vendor 是否异常

清单 8 --- 启动场景专项检查

很多 ANR 都发生在启动阶段,因为这时最容易把初始化、配置加载和首屏渲染堆在同一条时间线上。

  • Application 是否初始化过多
  • 是否多个 ContentProvider 提前初始化
  • 首页首帧前是否大量读设置
  • 是否大量 decode Bitmap
  • 是否同步加载滤镜 / 贴纸 / 预设 / 配置
  • 线程池、日志、DB、网络 SDK 初始化是否过重
  • 是否等待某异步初始化完成才继续显示页面
  • onCreate 是否做了重型 Binder

修复方向 :延迟初始化、按需初始化、首屏最小可用 、非关键任务挪到首帧后。


清单 9 --- UI 卡顿导致 ANR 的专项检查

有些 ANR 的根因并不是线程互等,而是 UI 链路本身过重,最终把响应时限拖穿。

  • 布局层级是否过深
  • RecyclerView item 是否过重
  • onBindViewHolder 是否耗时
  • 自定义 View 的 onMeasure / onLayout / onDraw 是否重算
  • 是否频繁创建对象导致 GC 抖动
  • 是否过度刷新 UI
  • 页面切换是否一次性加载过多模块

清单 10 --- CPU / 内存 / GC / IO 环境检查

ANR 不一定是「逻辑卡死」,也可能是环境拖慢。

  • ANR 时 CPU 是否打满
  • 系统整体负载是否很高
  • 是否频繁 GC、大内存分配
  • 磁盘是否写满 / 空间不足
  • I/O wait 是否很高
  • 低内存回收导致系统抖动

特别注意(相机场景) :存储空间不足、媒体写入异常、日志写爆等,都可能把时序拖垮,最终表现为 ANR。


清单 11 --- 日志排查

日志不能替代 trace,但它往往能帮助你把 trace 里的"发生了什么"补齐成"为什么会这样"。

logcat 重点关键词

ANR in、Reason:、Input dispatching timed out、Broadcast of Intent、executing service、ContentProvider not responding、am_anr、traces、ActivityManager、InputDispatcher、WindowManager、Choreographer、Skipped frames、binder、deadlock

相机项目额外搜

CameraService、cameraserver、configureStreams、openCamera、closeCamera、Session、Surface、ImageReader、EGL、BufferQueue、Frame available


清单 12 --- Perfetto / Systrace

如果能抓到 Perfetto 或 Systrace,很多"看起来像猜"的判断会立刻变得具体。

看什么
  • 主线程是长时间没切出去还是一直忙
  • 是否被 Binder block 、被锁卡住
  • 是否有阶段极长
  • 首帧前有哪些大块任务
  • camera / render / binder / main 的时间关系
重点线程

main、RenderThread、Binder:*、业务 HandlerThread、GLThread、相机相关线程、线程池 worker。


清单 13 --- 修复方案

定位完成之后,修复不要只停在"改成子线程"这类表面动作,而要回到真正的责任链上。

  • 是否真正移出主线程
  • 主线程是否还在等待子线程结果
  • 是否缩小锁范围
  • 是否去掉锁内 IO / Binder
  • 是否延迟初始化
  • 是否有缓存兜底
  • 是否减少同步跨进程调用
  • 是否拆分大任务
  • 是否加超时 与失败降级

清单 14 --- 验证

修复完成并不意味着问题已经结束,验证的目标是确认"ANR 消失"与"体验恢复"同时成立。

  • 原路径是否不再 ANR
  • 是否仍有明显卡顿
  • 是否引入新时序 问题、状态不一致
  • 是否在低端机验证
  • 是否在低存储 / 高负载场景验证
  • Monkey / 压测 / 长稳是否覆盖
  • 是否覆盖切前后台、旋转、锁屏、权限弹窗等边界

清单 15 --- 复盘时必须回答的 5 个问题

一份完整的 ANR 复盘,最好最终都能落到下面 5 个问题上:

  1. ANR 类型是什么?
  2. 主线程当时卡在哪里?
  3. 真正责任线程 / 责任模块是谁?
  4. 根因是线程模型、锁、Binder、IO、UI 还是启动过重?
  5. 修复后如何证明问题消失?

清单 16 --- 团队可直接套用的 ANR 分析模板

text 复制代码
【ANR 类型】
Input dispatching timed out / Broadcast / Service / Provider / Foreground service ...

【发生场景】
例如:进入录像模式后点击开始录像,低存储场景概率复现

【现场信息】
机型:
系统版本:
App 版本:
发生时间:
复现概率:

【主线程堆栈】
main 线程阻塞在:
- ...
- ...

【责任线程分析】
主线程正在等待:
持锁线程 / 对端线程为:
该线程正在执行:

【根因分析】
例如:
1. 主线程在启动录像时同步等待底层初始化结果;
2. 底层线程持有状态锁同时执行耗时 IO;
3. 主线程超过输入响应时限,触发 Input dispatching timed out。

【修复方案】
1. 将 xxx 移至后台线程;
2. 去除主线程同步等待;
3. 缩小锁粒度,避免锁内 IO;
4. 增加失败兜底与超时保护。

【验证结果】
1. 原场景连续复现 xx 次未再出现;
2. Monkey / 压测 xx 小时未出现;
3. 页面响应正常,无额外功能异常。

清单 17 --- 排查口诀

日常脑内快速过一遍:

先看类型,再看 main;main 在等,就找责任线程;main 在跑,就找重活代码;锁、Binder、IO、启动,是四个最高频方向。

与主栈对照时可用(细一点):

ANR 先看 main,main 不行看锁,锁不行看 Binder,Binder 不行看对端,最后回到线程模型。


第三部分 · 逐行阅读 traces.txt:锁等待 / Binder / 死锁

目标 不是「看懂每一行」,而是做到三件事:快速找到 ANR 关键线程 ;判断是锁等待 / Binder 阻塞 / 死锁 ;顺着线程关系把根因找出来 。

阅读原则:不要从上往下机械读完 ;顺序永远是「先进程 → 再 main → 再按栈定性 → 再追责任线程/对端」。
锁等待
Binder 调用
Future/get/await
UI/IO/重计算
nativePollOnce/看似空闲
拿到 traces.txt
先找发生 ANR 的进程
先看 main 线程
main 卡在哪里?
找持锁线程
找对端服务/进程
找被等待的任务线程
定位具体耗时代码
继续看其他关键线程
判断是否锁竞争或死锁
判断是否 Binder 阻塞
判断是否线程模型错误
给出优化方案
结合 logcat 和系统进程 trace 继续定位


1. traces.txt 里「一个线程块」怎么看

典型形态如下(示意,版本间细节可能略有差异):

text 复制代码
"main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x7381a000 self=0x7f8b2c0000
  | sysTid=12345 nice=-10 cgrp=top-app sched=0/0 handle=0x7fa1bcd4f8
  | state=S schedstat=( ... ) utm=10 stm=2 core=6 HZ=100
  | stack=0x7fd1234000-0x7fd1236000 stackSize=8192KB
  | held mutexes=
  native: #00 pc ...  /apex/.../libc.so (__epoll_pwait+8)
  ...
  at android.os.MessageQueue.nativePollOnce(Native method)
  at android.os.Looper.loop(...)
  at android.app.ActivityThread.main(...)

建议优先看这 5 类信息:

关注点 作用
线程名 "main"、RenderThread、Binder:...、FinalizerDaemon、业务名如 CameraHandler、pool-3-thread-1 → 判断角色
sysTid 系统线程号;可与 log、Perfetto、native trace 对齐
state= 第一眼参考:不要单靠 state 下结论 ,必须与 Java/Native 栈一起看
held mutexes= 若有内容,说明线程可能持有 native mutex,往往很有价值
调用栈(Java / Native,尤其栈顶附近) 核心:在干什么 、是自己干活 还是等别人
state= 常见取值(先建立直觉)
值 大致含义
R Running,可运行 / 正在跑
S Sleeping,休眠 / 等待
D 不可中断睡眠,常与内核等待、IO 相关
B Blocked
W Waiting
T Stopped

2. 拿到 traces.txt 后的第一步

第 1 步:先锁定发生 ANR 的进程

文件中常出现多段进程,格式类似:

text 复制代码
----- pid 12345 at 2026-04-16 10:21:33 -----
Cmd line: com.example.app

确认 pid / Cmd line / 时间点是否为你的 App。若怀疑系统侧,还要继续看 system_server、cameraserver、相关 Provider/Service 进程段。

第 2 步:在该进程内先搜 "main"

第一入口永远是主线程 。看:main 栈顶是什么------属于 wait / transact / sleep / inflate / query / decode 等哪一类场景。


3. 如何判断「锁等待」

锁等待在 ANR 里非常高频。

3.1 典型栈上关键词

waiting to lock、waiting for monitor entry、blocked on、Object.wait(需结合上下文)、LockSupport.park、ReentrantLock.lock、CountDownLatch.await、FutureTask.get、Thread.join 等。

3.2 最关键的一行(Java 监视器锁)

若出现类似:

text 复制代码
- waiting to lock <0x08a1b2c3> (a java.lang.Object) held by thread 42

含义一目了然:

  • 当前线程在等一把锁
  • 锁对象 <0x08a1b2c3>
  • 持锁线程是 thread 42

下一步 :立刻在全文搜 thread 42 或 <0x08a1b2c3>,看持锁线程在干什么。

3.3 持锁线程在干什么的典型结论

若持锁线程栈里出现 - locked <0x08a1b2c3> 且正在 IO / 解析 / 重逻辑 ,而 main 在同锁上 waiting to lock,则根因往往是:后台持锁过久 + 锁粒度过大,而不是「主线程那一行业务代码写得慢」。


4. Object.wait() / await / get / join(同步等异步)

Object.wait()

主线程若在 wait() 上卡住,本质是在等条件/结果/通知 ,不一定是传统「抢锁」问题。要继续看:waitResult() 一类逻辑在等谁 、谁负责 notify/notifyAll、对应线程是否执行到 、是否存在永远不发信号。

CountDownLatch.await() / Future.get() / Thread.join()

可视为高危信号 :几乎都是主线程同步等待后台完成。继续查:

  • 后台线程是否真的开始跑
  • 线程池是否满、任务是否被串行堵死
  • 后台是否反过来等主线程(环路)
  • 任务内是否有 IO / Binder / 锁

5. 如何判断「Binder 阻塞」

相机、媒体、系统服务交互多时尤其常见。

5.1 主线程典型栈特征

例如:BinderProxy.transact / transactNative / Binder.execTransact、ContentResolver.query、ActivityManager、PackageManager、CameraManager、SurfaceControl、MediaProvider、SettingsProvider 等。

理解 :主线程发起 Binder ,对端(如 CameraService)未及时返回 → 主线程被卡住。

5.2 继续往下看的思路

锁等待
IO/DB
再调别的 Binder
死锁
main 卡在 BinderProxy.transact
从栈识别接口/服务
判断对端进程是谁
查对端进程 trace / log
对端在干什么?
继续追锁
对端慢操作
链式 Binder 阻塞
含跨进程死锁

5.3 如何从栈里猜对端
栈上线索 对端倾向
ICameraService$Stub$Proxy... cameraserver 等相机链路
IActivityManager$Stub$Proxy.startService 等 system_server
ContentResolver.query Provider 所在进程 或系统 Provider
5.4 Binder 卡住的常见根因
  • 对端自身慢(DB、磁盘 IO、大计算、等锁)
  • 对端 Binder 线程池忙满,请求排队无人处理
  • 对端又同步调回你(跨进程环路等待)
  • 对端死锁/卡死(如 cameraserver、system_server)

结论 :有时 App main 只是受害者 ,真正慢在 cameraserver / HAL / vendor ,需结合对端 trace 与日志,不能只改 UI 线程表面逻辑。


6. 如何判断「死锁」

一句话 :A 等 B,B 又等 A,或形成环路等待,即死锁。

6.1 trace 上的典型形态(双向)
text 复制代码
"main" ...
  at com.xxx.ManagerA.call(ManagerA.java:45)
  - locked <0x1000>
  - waiting to lock <0x2000> held by thread 21

"Worker-21" ...
  at com.xxx.ManagerB.call(ManagerB.java:61)
  - locked <0x2000>
  - waiting to lock <0x1000> held by thread 1

若 thread 1 即 main:main 持有 <0x1000> 等 <0x2000> ,Worker 持有 <0x2000> 等 <0x1000> → 经典双向死锁。
等待 lock2000
被 Worker 持有
等待 lock1000
被 main 持有
main 持有 lock1000
lock2000
Worker 持有 lock2000
lock1000

6.2 常见模式速记
模式 说明
锁顺序不一致 线程 A:先 A 锁再 B 锁;线程 B:先 B 锁再 A 锁
主线程等后台,后台又等主线程 如 Future.get() + 后台 runOnUiThread 再同步等 UI
跨进程 App 主线程同步 Binder 调 system_server,对端又同步 Binder 回调你的进程,关键线程占满 → 互等(需多进程 trace 一起看)

7. 逐行读的「固定套路」(四步)

  1. 只看 main 栈顶约 10~20 行 ,扫关键词:锁类(waiting to lock...)、Binder 类(BinderProxy.transact...)、IO 类(read/SQLite/commit...)、UI 类(inflate/performTraversals...)。
  2. 给 main 定性:锁等待 / Binder / 同步等异步 / 主线程重活 / 看似空闲(需扩查)。
  3. 顺着被等待对象追 :held by thread X、锁地址、Future 所在线程池、Binder 对端进程与对端栈顶。
  4. 画等待链 (纸笔即可):main → 等 lock1 → CameraThread 持有 → ... 链一清,根因往往就明。

8. 三类压缩示例

示例 A:锁等待(锁内 IO)
  • main :waiting to lock <0x0a1b2c3d> held by thread 39
  • thread 39(如 CameraWorker) :- locked <0x0a1b2c3d> 且栈在 FileInputStream.read / ConfigParser.parse
  • 结论 :持锁线程在锁内做 IO + 解析 ,主线程抢同锁 → 锁粒度过大。
  • 方向 :锁内只保护共享状态;IO / parse 移出锁;减少主线程参与该锁。
示例 B:Binder 阻塞(openCamera)
  • main :BinderProxy.transact → ICameraService...connectDevice → CameraManager.openCamera → 业务 openRearCamera
  • 结论 :主线程卡在 Binder 未返回 ;需继续看 cameraserver / HAL 是否 wait、配流卡住等。
  • 方向 :App 侧避免主线程高风险同步;底层查 session 串行、锁、配流超时 等。
示例 C:死锁
  • main :locked <0x1000> 且 waiting to lock <0x2000> held by thread 21
  • Worker-21 :locked <0x2000> 且 waiting to lock <0x1000> held by thread 1
  • 结论 :标准双向死锁。
  • 方向:统一锁顺序、减少嵌套锁、主线程少持复杂业务锁、锁内不做回调与慢操作。

9. 容易误判的几点

现象 说明
nativePollOnce + Looper.loop 常表示在等消息,不等于一定无问题;需结合是否消息未投递、他线程持锁、抓取时刻偏差、对端阻塞等
state=Runnable 不一定真在跑业务;可能仍在 native poll 或内核等待 → 以栈顶方法为准
主线程栈「不慢」 仍可能是:后台持锁导致 main 抢不到、Binder 线程堵了 system_server、Provider 卡别人等 → 责任不一定在 main 栈顶那一行

10. 建议每次排 traces 写下来的模板 + 关键词速查

固定六步(可复制)
text 复制代码
1. ANR 进程
   进程名:
   pid:
   时间:

2. main 线程栈顶
   main 卡在:
   属于:锁等待 / Binder / await / IO / UI / 计算

3. 被等待对象
   锁对象 / Future / Latch / Binder 服务:

4. 责任线程
   线程名:
   tid:
   正在做什么:

5. 是否存在环路等待
   main -> X -> Y -> main ?

6. 根因结论
   锁粒度过大 / 主线程同步等异步 / Binder 对端慢 / 死锁 / 启动重活 / ...
「看到这些先想什么」速查
看到这些 先想
waiting to lock、held by thread、blocked on、locked <0x...> 锁
Object.wait、CountDownLatch.await、FutureTask.get、Thread.join、Semaphore.acquire、LockSupport.park 主线程同步等待
BinderProxy.transact、transactNative、ContentResolver.query、ICameraService、IActivityManager、IPackageManager Binder
inflate、performTraversals、measure/layout/draw、BitmapFactory.decode、SQLite、SharedPreferences.commit、JSONObject 主线程重活
A 等 B 的锁,B 又等 A 的锁 死锁
一句话抓住本质

看 traces.txt,不是看「哪个函数名熟悉」,而是看 「谁在等谁,谁又迟迟不返回」 。
实战版 :先看 main ,判断是在干活 还是在等 ;若在等,就一路顺到最后真正卡住的那个线程/对端。

更多带 log + 双线程/多进程栈 + 归因与修复 的完整拆解,见下方 [第四部分 · 真实案例拆解](#第四部分 · 真实案例拆解)。


第四部分 · Android ANR traces.txt 真实案例拆解

下面几组案例都按同一条路径展开:先给简化但真实风格的 trace → 再解释怎么看 → 然后落到结论与修复思路。顺序上先从普通 App 场景开始,再进入更复杂的工程实战场景。类名与包名仅为教学示意,实际项目请以你方代码与现场栈为准。


案例一:普通 App 点击后同步查库导致 ANR

先从一个最常见、也最容易建立直觉的场景开始:没有锁、没有 Binder、也没有系统进程,就是主线程自己在做不该做的事。

1. ANR 现场(log)
text 复制代码
ANR in com.example.notes
Reason: Input dispatching timed out

用户点击搜索或进入列表页后出现。

2. traces.txt 片段

main 线程

text 复制代码
"main" prio=5 tid=1 Runnable
  | sysTid=14620 ...
  at android.database.sqlite.SQLiteConnection.nativeExecuteForCursorWindow(Native method)
  at android.database.sqlite.SQLiteConnection.executeForCursorWindow(SQLiteConnection.java:1000)
  at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:838)
  at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
  at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:153)
  at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:140)
  at androidx.room.RoomSQLiteQuery.acquire(RoomSQLiteQuery.java:...)
  at com.example.notes.data.NoteDao_Impl.search(NoteDao_Impl.java:84)
  at com.example.notes.ui.SearchViewModel.load(SearchViewModel.java:52)
  at com.example.notes.ui.SearchActivity.onClick(SearchActivity.java:118)
  ...
  at android.app.ActivityThread.main(ActivityThread.java:...)
3. 这段 trace 怎么看
  • 栈里直接出现了 SQLite / Room DAO / 业务点击链路。
  • 这里没有 waiting to lock,也没有 BinderProxy.transact,说明它不像是在等别人。
  • 第一反应应该是:主线程自己在查库,而且这次查询耗时已经长到挡住了输入响应。
4. 根因结论
结论 说明
直接表述 点击后在主线程同步查库,查询慢时直接把 UI 卡住
本质 这是主线程重活,不一定需要追别的线程,先把这条调用链移开
5. 修复思路
  • 查询放到后台线程或协程里,不要在点击回调里同步拿结果。
  • 页面先展示加载中 / 缓存结果,不要"查完再显示"。
  • 如果是搜索、筛选、分页,优先检查是否能加索引、缩小结果集。
6. 这一类先怎么判断

如果 main 栈顶已经直接落在 SQLite / Room / 文件 / JSON 解析 / Bitmap 解码 这类方法上,往往先按主线程重活处理,不要先怀疑死锁或系统服务。


案例二:主线程抢锁导致 ANR

最常见的一类 :表面是主线程卡住,往往是后台线程持锁过久,主线程在抢同一把锁。

1. ANR 现场(log)
text 复制代码
ANR in com.example.camera
Reason: Input dispatching timed out

在这类输入超时场景里,第一反应仍然应该是先看 main。

2. traces.txt 片段

main 线程

text 复制代码
"main" prio=5 tid=1 Blocked
  | sysTid=18642 ...
  at com.example.camera.state.CameraStateRepository.getCurrentState(CameraStateRepository.java:142)
  - waiting to lock <0x0b7a4c11> (a java.lang.Object) held by thread 48
  at com.example.camera.ui.CameraActivity.updateModeUi(CameraActivity.java:820)
  at com.example.camera.ui.CameraActivity.onResume(CameraActivity.java:645)
  at android.app.Activity.performResume(Activity.java:...)
  ...
  at android.app.ActivityThread.main(ActivityThread.java:...)

thread 48(Preset-Loader)

text 复制代码
"Preset-Loader" prio=5 tid=48 Runnable
  | sysTid=18791 ...
  at java.io.FileInputStream.readBytes(Native method)
  at java.io.FileInputStream.read(FileInputStream.java:386)
  ...
  at com.example.camera.preset.PresetParser.readJson(PresetParser.java:96)
  at com.example.camera.preset.PresetParser.parse(PresetParser.java:61)
  at com.example.camera.state.CameraStateRepository.reload(CameraStateRepository.java:118)
  - locked <0x0b7a4c11> (a java.lang.Object)
  at com.example.camera.preset.PresetDataManager.refreshPreset(PresetDataManager.java:203)
  ...
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
  ...
3. 这段 trace 怎么看
  • main 关键句:- waiting to lock <0x0b7a4c11> held by thread 48 → 主线程在等锁,持锁方是 thread 48 ;此时不必先在 onResume 业务细节上纠结,应立刻跳到 thread 48。
  • thread 48 :- locked <0x0b7a4c11>,且栈在 读文件 + JSON 解析 (FileInputStream.read → PresetParser → CameraStateRepository.reload)。
4. 根因结论
结论 说明
直接表述 不是「主线程那几行代码慢」,而是 后台持锁时间过长,主线程抢锁失败,最终输入超时 ANR
本质 常伴随 锁粒度过大 、锁内 IO/解析 、主线程也访问同锁保护的数据
5. 等待关系(示意)

等待 lock 0x0b7a4c11
被持有
锁内
main updateModeUi
共享状态锁
Preset-Loader
文件读取
JSON 解析

6. 修复思路

不是 笼统说「改异步」------任务已经在后台线程 跑了;要动的是 锁的范围与职责:

  • 缩小锁范围 :锁只保护共享状态 的读写;读文件、解析 JSON 放锁外。
  • 主线程少碰重锁 :尽量拿快照 ;后台更新完后原子替换引用。
  • 避免锁内 链式 refresh → parse → reload → notify 一长串。
7. 伪代码对照

不推荐(锁内 IO + 解析)

java 复制代码
synchronized (mStateLock) {
    String json = readFile();
    State state = parse(json);
    mCurrentState = state;
}

更合理(先 IO/解析,再短锁写状态)

java 复制代码
String json = readFile();
State newState = parse(json);
synchronized (mStateLock) {
    mCurrentState = newState;
}

案例三:openCamera() Binder 阻塞导致 ANR

相机项目里极常见 :栈顶像是 CameraActivity.openCurrentCamera(),实质常是 openCamera 这条 Binder 链路迟迟未返回;慢点可能在 cameraserver / HAL / vendor 、session close/open 串行等。

1. ANR 现场(log)
text 复制代码
ANR in com.example.camera
Reason: Input dispatching timed out

典型场景:打开相机 或切前后摄时发生。

2. traces.txt 片段(App main)
text 复制代码
"main" prio=5 tid=1 Native
  | sysTid=21456 ...
  at android.os.BinderProxy.transactNative(Native method)
  at android.os.BinderProxy.transact(BinderProxy.java:584)
  at android.hardware.ICameraService$Stub$Proxy.connectDevice(ICameraService.java:1221)
  at android.hardware.camera2.CameraManager.openCameraDeviceUserAsync(CameraManager.java:712)
  at android.hardware.camera2.CameraManager.openCameraForUid(CameraManager.java:933)
  at android.hardware.camera2.CameraManager.openCamera(CameraManager.java:789)
  at com.example.camera.device.CameraDeviceController.open(CameraDeviceController.java:264)
  at com.example.camera.mode.BaseCameraMode.openCamera(BaseCameraMode.java:531)
  at com.example.camera.ui.CameraActivity.openCurrentCamera(CameraActivity.java:1186)
  at com.example.camera.ui.CameraActivity.onResume(CameraActivity.java:708)
  ...
  at android.app.ActivityThread.main(ActivityThread.java:...)
3. 怎么读

看到 BinderProxy.transact + ICameraService...connectDevice + CameraManager.openCamera → 定性为 Binder 阻塞 :主线程调 CameraService ,对端未及时返回,主线程被挂住 → 输入 ANR。

4. 只看 App trace 够不够?

不够。 App 侧只能说明卡在 openCamera,不能单独说明为什么慢。需联动:

  • cameraserver 与相关 Binder 线程栈
  • system log / vendor camera log
  • 若有 system_server trace 一并看
5. 对端 trace 的典型样子(示意)
text 复制代码
"Binder:camera_service" prio=5 tid=32 Native
  | sysTid=1423 ...
  at __futex_wait_ex(Native method)
  ...
  at android::camera3::Camera3Device::configureStreamsLocked(...)
  at android::Camera3Device::initializeCommonLocked(...)
  at android::CameraService::connectHelper(...)

或 vendor log 表现为:配流卡住 、上一 session close 未结束、等 buffer/stream configure 等。

6. 根因结论
层级 结论
App trace 能确定 主线程同步 openCamera(),被 Binder 挂住
需对端/日志才能定 配流慢 、close/open 串行阻塞 、HAL 锁竞争 、vendor pipeline 初始化慢 、底层死等条件 等
7. 等待关系(示意)

Binder openCamera
等 configure / close done
未及时返回
Binder 未返回
App main
cameraserver
HAL / Vendor

8. 修复思路
层面 方向
App 尽量避免在 onResume 等路径上主线程同步 发起整条 open 链路;主线程只触发流程 ,少承担可阻塞的重链路 ;注意 mode/camera 切换重入 ,避免 close/open 交叠
底层链路 查 close → open → configure 是否串行堵死;cameraserver/HAL 锁与回调;buffer/surface 是否未及时就绪
9. 常见误区

看到栈里有 CameraActivity.openCurrentCamera() 就说「Activity 代码慢」不准确 。更贴切的说法是:主线程阻塞在 CameraService Binder 返回;真正慢点常在服务端或底层链路 ------归因时要写清楚,避免误伤 UI 一行代码。


案例四:Future.get() 主线程等后台导致 ANR

这一类问题常被误以为"已经异步化了",但只要主线程立刻 get(),本质上仍然是同步阻塞。

1. ANR 现场(log)
text 复制代码
ANR in com.example.camera
Reason: Input dispatching timed out

场景:进页面、切模式 、点击等偶发。

2. traces.txt 片段

main

text 复制代码
"main" prio=5 tid=1 Waiting
  | sysTid=23651 ...
  at jdk.internal.misc.Unsafe.park(Native method)
  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
  at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:447)
  at java.util.concurrent.FutureTask.get(FutureTask.java:190)
  at com.example.camera.config.RemoteConfigCenter.getConfigSync(RemoteConfigCenter.java:153)
  at com.example.camera.mode.VideoMode.initFeatureFlag(VideoMode.java:218)
  at com.example.camera.mode.VideoMode.onActivate(VideoMode.java:171)
  at com.example.camera.ui.CameraActivity.switchMode(CameraActivity.java:1320)
  at com.example.camera.ui.CameraActivity.onClick(CameraActivity.java:1266)
  ...
  at android.app.ActivityThread.main(ActivityThread.java:...)

pool-3-thread-1(示意)

text 复制代码
"pool-3-thread-1" prio=5 tid=51 Runnable
  | sysTid=23740 ...
  at android.database.sqlite.SQLiteConnection.nativeExecuteForCursorWindow(Native method)
  ...
  at com.example.camera.config.RemoteConfigDao.queryAll(RemoteConfigDao.java:88)
  at com.example.camera.config.RemoteConfigCenter.lambda$getConfigSync$2(RemoteConfigCenter.java:146)
  ...
  at java.util.concurrent.FutureTask.run(FutureTask.java:264)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
  ...
3. 第一眼定性

Unsafe.park + FutureTask.awaitDone / FutureTask.get → 主线程在同步等后台任务结束 。不一定是锁或 Binder,但同样会卡死主线程。

4. 关系说明

用户 切模式 → switchMode → getConfigSync() 内部虽用线程池,但 main 上 Future.get() → 工作线程在跑 SQLite (queryAll)→ 查询慢或阻塞 → get() 一直等 → ANR。

5. 等待关系(示意)

Future.get 等结果
SQLite
慢
未返回
main switchMode
线程池任务
数据库

6. 根因结论

伪异步 :任务在后台跑,但主线程立刻同步等结果 ,本质仍是主线程阻塞。

7. 修复方向

错误模式

java 复制代码
Future<Config> future = executor.submit(() -> loadConfig());
Config config = future.get(); // 主线程阻塞
applyConfig(config);

正确方向 :真正异步(完成后再 回调/切主线程更新 UI );先读缓存再异步刷新 ;避免在点击、切模式、onResume 等链路里硬等远程/本地配置。

示意

java 复制代码
Config cache = mConfigCache;
if (cache != null) {
    applyConfig(cache);
}
executor.execute(() -> {
    Config fresh = loadConfig();
    mConfigCache = fresh;
    mainHandler.post(() -> applyConfigIfNeeded(fresh));
});

四类快速区分(读 trace 时第一反应)

类型 栈上常见痕迹 第一反应
主线程重活 SQLite、Room、BitmapFactory.decode、JSONObject、read、performTraversals 先看能否直接移出主线程
锁等待 waiting to lock、held by thread、locked <0x...> 找持锁线程在干什么
Binder 阻塞 BinderProxy.transact、ICameraService、IActivityManager、ContentResolver.query 找对端进程/服务与对端栈
主线程等后台 FutureTask.get、CountDownLatch.await、Thread.join、Object.wait、Semaphore.acquire 找被等待的任务线程及栈顶慢点

真实排查时的固定读法(五步走)

  1. 只看 main 先问两句 :是在自己干活 ,还是在等别人?
  2. 给 main 贴一个优先标签:锁等待 / Binder / await·get·join / IO·DB / UI / 重计算。
  3. 顺藤摸瓜:锁 → 持锁线程;Binder → 对端进程;get/await/join → 后台任务线程。
  4. 画等待链 (纸笔即可),例如:main → 等 lockA → Worker 持锁内读文件,或 main → Binder → cameraserver → HAL。
  5. 再回代码定修复点 :先把责任链走通 ,避免一上来只盯 main 栈顶业务函数名。

最后用两句话收束

main 卡锁,看持锁线程;main 卡 Binder,看对端服务;main 卡 get/await,看后台任务。

再压缩一句:

谁让 main 等,谁就是重点。


附录 A --- 常用命令与工具

类别 命令 / 工具
日志 adb logcat -d > logcat.txt
ANR 文件 adb shell ls /data/anr → adb pull /data/anr/anr_xxx ./anr.txt
Activity / 输入 / CPU adb shell dumpsys activity > activity.txt、dumpsys input > input.txt、dumpsys cpuinfo > cpuinfo.txt
综合现场 bugreport(条件允许必拉)
调度与耗时 Systrace / Perfetto

重点看什么:主线程栈、Binder 链、锁等待关系、CPU 是否打满、磁盘 IO、首帧与页面切换时序。


附录 B --- 修复手段优先级与反面示例

优先级 手段 要点
1 耗时任务移出主线程 IO、DB、网络、解码、JSON、大对象、相机参数、算法预处理等;主线程不能再同步等结果
2 锁粒度与用法 主线程少参与锁;锁内不做 IO/Binder/UI 回调/复杂逻辑
3 启动优化 Application 少堆初始化;延迟与懒加载;Provider 轻量;避免主线程同步等 SDK
4 Binder 可能慢的调用不放主线程;批量代替频繁小调用;能缓存先缓存
5 UI 布局扁平;首屏分阶段;RecyclerView 用 payload;减过度绘制与频繁 requestLayout

反面示例:「移线程」却仍阻塞主线程

java 复制代码
Future<Result> future = executor.submit(task);
Result result = future.get(); // 主线程仍被阻塞
java 复制代码
synchronized (lock) {
    readFile();
    queryDb();
    remoteService.call();
}

一句话总结

解决 ANR 不是「稍微优化性能」,而是保证关键线程在时限内不被阻塞。

  • 主线程只做必须做的事;不等不确定耗时的结果
  • 锁里不做慢操作;跨进程调用默认可能慢;初始化默认可能过重
  • 先看线程关系,再抠优化细节
相关推荐
martindelophy41 分钟前
用自然语言剪视频:我们在 Timeline Studio 中实现了 ChatCut 对话剪辑
android·音视频
ao-weilai1 小时前
MySQL数据库:复合查询
android·数据库·mysql
三少爷的鞋14 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)18 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码20 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程20 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer