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 栈采集等线程挂起操作。
相关推荐
千里马学框架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