Android main thread主线程Choreographer doFrame发生FullSuspendCheck

Android main thread主线程Choreographer doFrame发生FullSuspendCheck

1. FullSuspendCheck 是什么

ART 的 Java/Kotlin 线程并不是任意时刻都能立刻安全暂停。通常线程需要执行到某些 ART 已知安全的位置,称为:

复制代码
safepoint
suspend check point

在这些位置,ART 会检查当前线程是否被要求暂停。通常情况下,这类检查很快,类似:

复制代码
是否有 suspend request?
没有。
继续执行。

但如果发现当前线程存在 suspend request,就会进入较重的路径:

复制代码
FullSuspendCheck

可大致理解为:

复制代码
ART 发现当前线程需要响应挂起请求。
当前线程进入可被安全检查、暂停或协调 runtime 操作的流程。

因此,FullSuspendCheck 不常见且耗时长,往往意味着当时不是普通代码执行,而是 ART runtime 正在进行某种需要线程配合的全局或半全局操作。

2. 为什么会发生在 Choreographer#doFrame 中

因为主线程正在运行 doFrame,而 ART 的 suspend check 可以插入或出现在 Java/Kotlin 执行过程中的多个安全点,例如:

复制代码
方法调用边界
循环回边
滑动、View traversal、绘制等逻辑
    ↓
运行到 ART safepoint
    ↓
ART 发现存在 pending suspend request
    ↓
进入 FullSuspendCheck
    ↓
主线程被暂停/等待/协助 runtime 操作
    ↓
返回后继续执行 doFrame

所以它出现在 doFrame 内,只是说明:

复制代码
主线程刚好在该帧执行期间响应了 ART 的挂起请求。

并不表示:

复制代码
Choreographer 或大图滑动代码直接调用了 FullSuspendCheck。

3. FullSuspendCheck 常见触发原因

从常见程度和关联度看,优先排查以下几类。

原因一:GC 相关的线程暂停

这是最需要优先确认的一类。

ART 在执行某些 GC 阶段时,需要让 Java 线程进入 safepoint,或者等待线程到达可安全扫描/处理的状态。

典型链路:

复制代码
大图左右滑动期间产生较多对象、Bitmap、Drawable、临时集合或图片解码相关对象
    ↓
Java heap / native heap 压力上升
    ↓
ART 发起 GC
    ↓
GC 某阶段要求 mutator 线程响应 suspend request
    ↓
图库主线程执行到 safepoint
    ↓
进入 FullSuspendCheck
    ↓
主线程暂停或等待 GC 相关操作
    ↓
doFrame 被拉长

注意:

复制代码
并发 GC 不代表对 UI Thread 完全没有影响。

即使 GC 的主体工作在后台线程上进行,仍可能有需要应用线程配合的阶段,或者存在短暂停顿。

应在同一时间窗口检查:

复制代码
HeapTaskDaemon
GC Thread
Concurrent GC
Marking
Sweep
Compact
Young GC
Explicit GC
WaitForGcToComplete

不同 Android 版本的 trace 名称会有区别。

原因二:线程创建、线程退出、Attach/Detach 等 ART ThreadList 操作

ART 维护一份 runtime 线程列表。

当线程创建、销毁,或 native 线程执行 JNI attach/detach 时,可能涉及 thread list、线程状态切换及相关同步。

如果应用频繁创建线程,例如:

复制代码
临时图片解码线程
协程调度器工作线程扩容
自建线程池临时建线程
JNI/native 图片处理线程 attach/detach
媒体/相机/相关 native 工作线程

就可能增加 ART runtime 线程管理操作。

这类路径不一定每次都导致全局暂停,但如果 trace 同期出现:

复制代码
Thread.start
ThreadList
AttachCurrentThread
DetachCurrentThread
CreateNativeThread
DestroyJavaVM

就需要重点关联。典型链路:

复制代码
某个调试、采样或监控组件请求线程栈
    ↓
ART 发起线程 suspend / checkpoint 操作
    ↓
主线程在 safepoint 响应
    ↓
出现 FullSuspendCheck

如果这是在:

复制代码
debug 包
开启 Android Studio Profiler
开启 method trace
开启 heap tracking
接入性能监控或崩溃监控实验功能

环境下复现,需要优先排除这类因素。

原因四:JIT、类加载、运行时内部。

4. 为什么会出现 FullSuspendCheck

可能有几种情况:

情况一:同一个 runtime 操作的多阶段协作

例如某次 GC 或 ART runtime 操作不是一次主线程暂停就完全结束,而是在不同阶段需要 mutator 线程再次配合。

复制代码
主线程第一次到达 safepoint
    ↓
第一次 FullSuspendCheck
    ↓
恢复执行
    ↓
GC/runtime 操作进入下一阶段
    ↓
主线程再次到达 safepoint
    ↓
第二次 FullSuspendCheck

情况二:短时间内连续出现两次独立 suspend 请求

例如:

复制代码
一次 GC 相关请求
    ↓
主线程恢复
    ↓
随后又发生线程管理、调试采样或另一轮 GC 相关请求
    ↓
主线程再次进入 FullSuspendCheck

情况三:主线程第一次没有立即完成所需协调,后续再次检查

这需要看具体 ART 版本及 trace 周边事件确认。

但总体含义仍然是:

复制代码
主线程在同一帧中多次被 runtime suspend 存在 pending suspend/checkpoint 请求
    ↓
主线程第一次运行到 safepoint
    ↓
进入 FullSuspendCheck,耗时约 20ms 的一部分
    ↓
主线程恢复后继续执行 doFrame
    ↓
再次运行到 safepoint
    ↓
再次进入 FullSuspendCheck
    ↓
doFrame 总耗时被拉长至 80ms+
    ↓
90Hz 下错过约 7 帧
    ↓
大图左右滑动明显卡顿

90Hz 帧预算:

复制代码
1000 / 90 ≈ 11.11ms

因此:

复制代码
80ms / 11.11ms ≈ 7.2 帧

而单次 20ms+FullSuspendCheck 本身已经超过一帧预算。

6. 在 Perfetto/Trace 中如何继续定位

FullSuspendCheck 的起止时间为中心,前后各扩 50ms~200ms 看完整时间线。

7.1 优先找 ART / GC 轨道

搜索这些关键词:

复制代码
GC
HeapTaskDaemon
HeapTask
Concurrent
MarkSweep
Sweep
Compaction
Marking
WaitForGcToComplete
SuspendAll
ResumeAll
Checkpoint
ThreadList

如果它们与 FullSuspendCheck 重叠,尤其是有:

复制代码
SuspendAll
GC
HeapTaskDaemon

那么 GC 相关性很高。

7.2 看进程中的线程状态

重点看这些线程在该时段是否活跃:

复制代码
HeapTaskDaemon
Jit thread pool
Signal Catcher
ReferenceQueueDaemon
    ↓
ART GC 或系统内存回收
    ↓
主线程 FullSuspendCheck
    ↓
滑动卡顿

7.4 排除调试和监控影响

确认复现环境是否有:

复制代码
Android Studio Profiler
CPU method trace
heap profiling
debugger attached
StrictMode
自研 ANR watchdog
自研线程栈采样
Crash/性能 SDK 的高频堆栈采集

建议对比:

复制代码
debug 包 + profiler 开启
debug 包 + profiler 关闭
release 包

如果只在 profiler/debug 环境下频繁出现,优先考虑调试采样造成的 runtime 挂起干扰。

8. 最终重点

最需要确认的是:

复制代码
这两次 FullSuspendCheck 同期,是否存在 GC / SuspendAll / HeapTaskDaemon 活动?

如果有,优先沿着:

复制代码
Bitmap/对象分配
    ↓
Java/native 内存压力
    ↓
GC/runtime suspend
    ↓
FullSuspendCheck
    ↓
UI Thread doFrame 卡顿

去定位。

如果没有 GC 痕迹,则下一优先级是:

复制代码
是否发生了协程/线程创建或 native Attach/Detach,
以及是否有 profiler、ANR/Crash 栈采集等线程挂起操作。
相关推荐
TimeFine5 小时前
智能眼镜开发:获取真实的音频路由
android
pengyu5 小时前
【Kotlin 协程修仙录 · 渡劫境 · 中阶】 | 造化神兵:自定义 CoroutineDispatcher 与调度器的终极定制
android·kotlin
pengyu5 小时前
【Kotlin 协程修仙录 · 渡劫境 · 初阶】 | 飞升雷劫:CPS 变换与挂起函数字节码终极透视
android·kotlin
TimeFine5 小时前
智能眼镜开发:眼镜Touch后收音与触发播放系统音乐的矛盾处理
android
TimeFine6 小时前
智能眼镜开发:眼镜侧收集音频
android
又见情义6 小时前
Android 系统设置从平板版迁移至TV版实践
android
杉氧9 小时前
打破边界(一):实战编写 Android/iOS 原生模块 (Native Modules)
android·react native·前端框架
baidu_2474386110 小时前
Android 35适配
android