Android main thread主线程Choreographer doFrame发生FullSuspendCheck
2026/9/8 14:28:46 网站建设 项目流程

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 栈采集等线程挂起操作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询