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

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

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


先建立一条最短排查路径

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

  1. 先确认 Reason / ANR 类型
  2. 立刻保留现场logcattraces.txtbugreport
  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.loopnativePollOnce不代表没问题,可能包括:

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

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


第二部分 · 实战排查清单

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

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

建议先确认的信息
  • ANR 时间点
  • 机型 / Android 版本
  • App 版本
  • 是否必现发生场景 (可多选对照)
    • 冷启动 / 切前后台 / 页面跳转
    • 点击拍照、录像 / 切换摄像头
    • 设置页打开 / 广播触发 / Service 启动
  • 是否只在某一项目、某一平台出现(如 MTKQualcomm 、特定 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. 锁等待
项目 内容
关键词 BLOCKEDwaiting to lockwaiting for monitor entryObject.waitLockSupport.parkCountDownLatch.awaitFutureTask.get
结论 主线程往往不是「自己慢」,而是在等别人
排查重点 持锁线程 ;找被 await/get任务线程 ;查是否死锁 / 环形等待
B. Binder 阻塞
项目 内容
关键词 BinderProxy.transacttransactNativeContentResolverPackageManagerActivityManagerCameraServiceSurfaceFlinger
结论 主线程在做跨进程调用 ,需查对端是否卡住
排查重点 能否移到后台线程 ;查 system_server / cameraserver / Provider 等对端日志
C. IO / DB
项目 内容
关键词 readwriteopenfsyncSQLiteSharedPreferences.commitBitmapFactory.decode
结论 主线程做了慢 IO大图解码
排查重点 异步化缓存 ;减少同步落盘;commit()apply()
D. UI 构建 / 绘制过重
项目 内容
关键词 performTraversalsmeasurelayoutdrawinflateRecyclerViewConstraintLayout
结论 页面太重刷新过频
排查重点 布局层级首屏初始化列表 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 inReason:Input dispatching timed outBroadcast of Intentexecuting serviceContentProvider not respondingam_anrtracesActivityManagerInputDispatcherWindowManagerChoreographerSkipped framesbinderdeadlock

相机项目额外搜

CameraServicecameraserverconfigureStreamsopenCameracloseCameraSessionSurfaceImageReaderEGLBufferQueueFrame available


清单 12 --- Perfetto / Systrace

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

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

mainRenderThreadBinder:*、业务 HandlerThreadGLThread、相机相关线程、线程池 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"RenderThreadBinder:...FinalizerDaemon、业务名如 CameraHandlerpool-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_servercameraserver、相关 Provider/Service 进程段。

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

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


3. 如何判断「锁等待」

锁等待在 ANR 里非常高频

3.1 典型栈上关键词

waiting to lockwaiting for monitor entryblocked onObject.wait(需结合上下文)、LockSupport.parkReentrantLock.lockCountDownLatch.awaitFutureTask.getThread.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.execTransactContentResolver.queryActivityManagerPackageManagerCameraManagerSurfaceControlMediaProviderSettingsProvider 等。

理解 :主线程发起 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)
  • mainwaiting to lock <0x0a1b2c3d> held by thread 39
  • thread 39(如 CameraWorker)- locked <0x0a1b2c3d> 且栈在 FileInputStream.read / ConfigParser.parse
  • 结论 :持锁线程在锁内做 IO + 解析 ,主线程抢同锁 → 锁粒度过大
  • 方向 :锁内只保护共享状态;IO / parse 移出锁;减少主线程参与该锁。
示例 B:Binder 阻塞(openCamera)
  • mainBinderProxy.transactICameraService...connectDeviceCameraManager.openCamera → 业务 openRearCamera
  • 结论 :主线程卡在 Binder 未返回 ;需继续看 cameraserver / HAL 是否 wait、配流卡住等。
  • 方向 :App 侧避免主线程高风险同步;底层查 session 串行、锁、配流超时 等。
示例 C:死锁
  • mainlocked <0x1000>waiting to lock <0x2000> held by thread 21
  • Worker-21locked <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 lockheld by threadblocked onlocked <0x...>
Object.waitCountDownLatch.awaitFutureTask.getThread.joinSemaphore.acquireLockSupport.park 主线程同步等待
BinderProxy.transacttransactNativeContentResolver.queryICameraServiceIActivityManagerIPackageManager Binder
inflateperformTraversalsmeasure/layout/drawBitmapFactory.decodeSQLiteSharedPreferences.commitJSONObject 主线程重活
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 48Preset-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.readPresetParserCameraStateRepository.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. 关系说明

用户 切模式switchModegetConfigSync() 内部虽用线程池,但 mainFuture.get() → 工作线程在跑 SQLitequeryAll)→ 查询慢或阻塞 → 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 时第一反应)

类型 栈上常见痕迹 第一反应
主线程重活 SQLiteRoomBitmapFactory.decodeJSONObjectreadperformTraversals 先看能否直接移出主线程
锁等待 waiting to lockheld by threadlocked <0x...> 找持锁线程在干什么
Binder 阻塞 BinderProxy.transactICameraServiceIActivityManagerContentResolver.query 找对端进程/服务与对端栈
主线程等后台 FutureTask.getCountDownLatch.awaitThread.joinObject.waitSemaphore.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/anradb pull /data/anr/anr_xxx ./anr.txt
Activity / 输入 / CPU adb shell dumpsys activity > activity.txtdumpsys input > input.txtdumpsys 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 不是「稍微优化性能」,而是保证关键线程在时限内不被阻塞。

  • 主线程只做必须做的事;不等不确定耗时的结果
  • 锁里不做慢操作;跨进程调用默认可能慢;初始化默认可能过重
  • 先看线程关系,再抠优化细节
相关推荐
chenjixue2 小时前
记录下我理解的安卓,鸿蒙,ios, rn , fullter, Jetpack Compose,react 的相似与不同
android·华为·harmonyos
REDcker2 小时前
Android ADB 命令教程与速查
android·adb
书中有颜如玉2 小时前
Kotlin Coroutines 异步编程实战:从原理到生产级应用
android·开发语言·kotlin
aq55356002 小时前
PHP7.2 vs 5.6:性能翻倍的关键升级
android
JJay.13 小时前
Android BLE 稳定连接的关键,不是扫描,而是 GATT 操作队列
android·服务器·前端
忒可君13 小时前
C# winform 自制分页功能
android·开发语言·c#
summerkissyou198713 小时前
Android-线程安全-volatile
android·线程
我命由我1234516 小时前
Android 开发中,关于 Gradle 的 distributionUrl 的一些问题
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
su_ym811016 小时前
Android 系统源码阅读与编译构建实战指南
android·framework