CnOps 智能运维与可观测社区
首页开源项目实践文章视频课程常见问答开发者工具STAROps 专题
导航菜单
首页开源项目实践文章视频课程常见问答开发者工具STAROps 专题

CnOps 智能运维与可观测社区

愿景

CnOps 智能运维与可观测社区是一个以"智能运维与可观测"为核心的开放、包容、分享的技术社区,旨在聚集运维专家、开发者和爱好者,共同探讨、学习和分享可观测最佳实践与最新技术,与众多技术社区合作互动,共同探讨交叉领域的技术挑战,推动可观测领域的创新与进步。

内容社区

  • 实践文章
  • 视频课程
  • 开源项目
  • 常见问答
  • 开发者工具

友情链接

  • Prometheus
  • Grafana Lab
  • OpenTelemetry
  • LoongCollector

关注我们

阿里云云原生公众号阿里云云原生
阿里云可观测公众号阿里云可观测

Copyright © 2026 CnOps 社区. All rights reserved.

首页实践文章用户说“App 卡死了”,你却查不到原因?可能是监控方式错了

用户说“App 卡死了”,你却查不到原因?可能是监控方式错了

#监控#指标#追踪#部署#网络

agenticOps | 2026-05-23

背景介绍

用户在使用App时,通常会遭遇以下典型场景:

  • 打开复杂页面时出现黑屏/白屏延迟
  • 列表滑动时偶发性卡顿
  • 图片加载时界面响应滞后
  • 网络请求密集时出现操作卡死等现象

这些场景不仅出现在低端设备上,在中高端机型中同样存在。如果主线程无法响应用户的交互就会造成卡顿,卡顿时间比较长是比较影响App的功能和用户体验的。在移动应用开发中,卡顿问题也始终是影响用户体验的核心痛点。

通常情况下,导致主线程阻塞并引发卡顿的原因主要有以下几种:

  • 繁重的UI渲染: 当界面包含复杂的视图层级、大量的图文混排内容时,计算布局和绘制到屏幕上的工作量会急剧增加,超出单次刷新周期的处理能力。
  • 主线程同步网络请求: 在主线程中发起同步的网络调用,意味着整个应用必须等待网络数据返回后才能继续执行,期间无法响应任何用户操作。
  • 大量的文件读写(I/O): 在主线程上直接进行大规模的数据读取或写入操作,例如读写数据库或本地文件,会因为磁盘速度的限制而消耗大量时间。
  • 高负荷的计算任务: 将复杂的算法或大量数据的处理逻辑直接放在主线程执行,会导致CPU持续处于高占用状态,无暇顾及UI事件。
  • 线程锁使用不当: 当主线程需要等待其他线程释放某个锁资源时,它会被挂起,如果等待时间过长,便会造成卡顿。在极端情况下,不同线程间的相互等待还会引发“死锁”,导致应用彻底无响应。

由于这些问题的偶发性和环境依赖性,传统的线下调试手段往往难以奏效。为了能够精确、高效地定位并解决这些线上卡顿,我们进行一些卡顿监控技术的探索。

主流卡顿监控方案

在iOS开发中,以下是几种常见的主流卡顿监控方案:

  • Ping线程方案
  • FPS监控方案
  • RunLoop监控方案

Ping线程方案简介

Ping线程方案的核心思想是:

  • 创建一个子线程,通过子线程来“探测”主线程的响应能力。
  • 子线程每次 ping 主线程时设置标记位为YES,然后派发任务到主线程中,主线程把标记位设置为NO。
  • 子线程sleep指定时间,超时后判断标记位是否设置为NO,如果没有说明主线程发生了卡顿。

如下图所示:

关键实现步骤:

  1. 创建子线程:启动一个独立的监控线程。
  2. 定时派发任务:子线程定期向主线程派发一个简单的任务,并设置一个等待标记。
  3. 等待主线程响应:主线程执行该任务时,会回调子线程,并清除等待标记。
  4. 超时判断:如果子线程在派发任务后的一小段时间内,发现等待标记仍未被清除,则判定主线程卡顿。
  5. 捕获与上报:执行堆栈捕获和上报流程。

Ping线程的方案逻辑相对比较简单,也比较容易理解。但精度较差,Ping之间可能存在漏查的情况。同时,Ping线程会不停唤醒主线程RunLoop,也会存在一定的性能损耗。

FPS监控方案简介

通常情况下屏幕会保持60Hz/s的刷新速度(新的iOS设备甚至会保持120Hz/s的刷新速度),每次刷新时会发出一个屏幕刷新信号,CADisplayLink允许开发者注册一个与刷新信号同步的回调处理。

我们可以通过计算它1秒内调用多少次来查看界面的流畅度。虽然CADisplayLink更轻量,但需要在CPU稍微清闲时才能够回调,严重卡顿的堆栈获取不一定及时,并且就算50fps以下通过肉眼来看也是连贯的。所以,简单的通过监控FPS很难确定是否出现了卡顿问题。

RunLoop方案简介

基于RunLoop的监控方案,是目前比较主流的可用于生产环境的监控方案。其原理是利用CFRunLoopObserver来观察主线程RunLoop的状态变化。这里通过引用戴铭关于RunLoop原理的图,来对RunLoop方案的原理进行简单介绍:

  • 通知 observers: RunLoop 即将开始“进入loop”
  • 随后,会开启一个do while来保活线程
    • 通知 obersers:RunLoop 会触发Timer、Source0回调,紧接着执行加入的block
    • 如果 Source1 是 ready 状态,会跳转到“处理消息”流程
  • 通知 observers:RunLoop 即将进入休眠状态
  • 等待 mach_port 消息,以再次唤醒
    • 基于 port 的 Source 事件
    • Timer 时间到
    • RunLoop 超时
    • 被调用者唤醒
  • 通知 observers:RunLoop 被唤醒
  • 处理消息
  • 继续下一个loop

基于RunLoop的方案实现中,一般会包含下面几个关键步骤:

  1. 注册Observer:向主线程RunLoop注册Observer来监听其状态
  2. 创建监控线程:用于监控主线程切换状态
  3. 状态标记与超时判断:子线程根据RunLoop的状态变化来设置标记,并循环检测该标记是否在预设的阈值内被更新,否则判定为卡顿。
  4. 堆栈捕获与上报:判定为卡顿时,捕获主线程的调用堆栈,并上报服务器进行分析。

基于RunLoop的方案能够精准捕获到由主线程阻塞导致的各类卡顿,适用于线上卡顿问题的监控和诊断分析。

方案对比

基于主流方案对比分析,三种性能监控策略分别聚焦不同维度,如下表:

对比维度

Ping线程方案

FPS监控方案

RunLoop方案

核心原理

子线程定期派发任务检测主线程响应能力

通过CADisplayLink统计单位时间回调次数计算FPS

通过CFRunLoopObserver监听状态切换超时事件

监控粒度

中等:依赖探测频率,可能遗漏间隔卡顿

较低:侧重平均表现,偶发严重卡顿易被平均

高精度:可捕捉单次长耗时阻塞事件

根因定位

中等:超时后捕获堆栈,但存在时机延迟

弱:仅反映流畅度结果,无法定位代码堆栈

强:超时时直接捕获主线程堆栈定位具体代码

性能开销

较低:子线程开销+轻微主线程负担,总体影响小

极低:CADisplayLink系统级轻量机制

较低:Observer回调轻量,仅卡顿时有额外处理

复杂度

中等:需处理线程管理和超时逻辑

低:基于计数与时间戳的简单实现

高:需深入RunLoop机制与多线程同步

适用场景

快速搭建基础卡顿监控

量化界面流畅度(如滚动/动画优化)

定位主线程阻塞(I/O、死锁、复杂计算等)

  • Ping线程方案通过子线程周期性探测主线程响应时间识别卡顿,但精度低于RunLoop方案。
  • FPS监控作为全局性能指标,通过帧率波动反映应用流畅度并判断卡顿,却无法定位性能瓶颈。
  • RunLoop方案则介入主线程事件循环机制,实现单次阻塞事件的毫秒级捕捉,可精准识别主线程阻塞源。

卡顿监控方案实现

卡顿监控方案的核心目标在于精准捕获并定位导致用户操作中断、体验显著下降的"阻塞型"卡顿。当卡顿发生时,不仅包括识别卡顿事件的发生,更需追溯具体代码行级的执行路径以定位问题根源。

相较于其他主流方案,RunLoop监控方案通过持续追踪主线程任务执行耗时,能够精确捕获卡顿事件并同步采集完整的上下文调用堆栈信息。尽管其技术实现复杂度较高,但考虑到可部署于线上环境,以及对卡顿根因的诊断价值,最终被选定为核心实现方案。

上文中已经对RunLoop的原理有过大致的介绍,下文中将主要介绍如何具体实现。

监控RunLoop状态

基于RunLoop实现卡顿监控方案,需要先监控RunLoop的状态切换。如下图,通过注册的Observer可以监听到主线程RunLoop状态的切换事件,并通过running和startTime来记录关联的状态和时间戳信息。监控线程通过读取running状态,以及startTime来判断是否产生了状态超时:

当主线程在执行某个任务的耗时较长时,RunLoop的状态切换就会延时。通过在子线程监控RunLoop关键状态之间的时间差,就可以判断主线程是否发生了阻塞。

方案实现中,

  • 当Observer收到kCFRunloopBeforeTimers、kCFRunloopBeforeSource、kCFRunLoopAfterWaiting通知时,会把running状态置为YES,并通过startTime记录当前时间戳。
  • 当Observer收到kCFRunloopBeforeWaiting和kCFRunLoopExit通知时,把running状态置为NO。
  • 监控线程需要持续读取running状态和startTime时间戳,通过判断当前时间与startTime的差异来确定是否发生了卡顿,如下图:

堆栈提取

当RunLoop状态超时,即检测到卡顿时,需要提取主线程堆栈并保存到内存中。堆栈的提取方案基于业界知名的KSCrash实现。相比通过系统函数获取堆栈,通过KSCrash获取的堆栈可以配合dSYM进行符号还原,能够定位到具体的代码位置,而且性能消耗也不大。

耗时堆栈提取

当监控线程监测主线程RunLoop时,会获取主线程的线程快照作为卡顿堆栈。但是,这个主线程堆栈不一定是最耗时的堆栈,也不一定是导致主线程超时的主要原因。为了对这个问题进行优化,需要在检测到主线程发生卡顿时,通过对保存在循环队列中的堆栈进行回溯(间隔50ms获取一次),获取最近最耗时堆栈。

如上图所示,通过以下特征找出最近最耗时堆栈:

  • 以栈顶函数为特征,栈顶函数相同的,整个堆栈就是相同的,如:
    • 堆栈A的栈顶调用函数为FuncA
    • 堆栈B的栈顶调用函数为FuncB
    • 栈顶函数FuncA与FuncB不同,因此堆栈A和堆栈B为不同的堆栈
  • 获取堆栈间隔相同,堆栈的重复次数近似作为堆栈的调用耗时,重复越多耗时越多,如:
    • 堆栈A重复一次,近似耗时为50ms
    • 堆栈B重复一次,近似耗时为50ms
    • 堆栈C重复三次,近似耗时为150ms
    • 堆栈C为最耗时堆栈
  • 重复次数相同的堆栈中,取最近的一个作为最耗时堆栈

监控线程退火算法

卡顿检测机制在无异常场景下性能开销可忽略,但遭遇持续数秒级卡顿时,频繁采集主线程堆栈信息时将引发显著性能损耗。且连续重复的堆栈记录无分析价值,完全没有必要。为了降低卡顿监测带来的性能损耗,SDK采用了退火算法递增时间间隔,避免因同一个卡顿问题带来的性能问题。

  • 每次子线程检测到主线程卡顿,会先获取主线程的堆栈,并保存到内存中
  • 把获得的主线程堆栈与上次卡顿获得的线程堆栈进行比对
    • 不同:获得当前线程快照并写入到文件
    • 相同:跳过,并按照斐波那契数列把检测时间递增,直到没有卡顿或卡顿堆栈不同

以上算法可以避免同一个卡顿写入多个文件的情况,避免检测线程遇到主线程卡死的情况下,不断写线程快照文件。

性能开销

任何一个监控工具的首要原则是不能影响被监控对象的性能。因此,我们还需要测量基于 RunLoop 的卡顿监控方案对应用性能的实际影响。性能测量的核心是进行A/B对比测试。我们需要准备两个几乎完全相同的App版本:

  • A版本(基准版本):卡顿监控功能完全禁用。
  • B版本(监控版本):卡顿监控功能完全开启。

然后在完全相同的设备和环境下,对这两个版本执行相同的操作,并测量关键性能指标的差异。这个差异就是卡顿监控带来的性能开销。

测试设备:iPhone 12 Pro

测试系统:iOS 18.7

卡顿监控功能没有开启的情况下,App持续运行一段时间并主动触发卡顿,App整体的CPU占用如下图:

卡顿监控功能开启的情况下,App持续运行一段时间并主动触发卡顿,App整体的CPU占用如下图:

在卡顿监控开启情况下,监控线程的CPU占用如下。

有卡顿发生时:

无卡顿发生时:

综上分析,App引入卡顿监控能力后:

  • 无卡顿发生时,对App性能几乎无影响
  • 有卡顿发生时,App整体CPU占用增加约0.33%(不同设备的测试值会略有差异)

总结

本文主要介绍了当下主流的iOS卡顿监控方案,和基于RunLoop的卡顿监控实现细节,包括RunLoop状态的处理,堆栈以及耗时堆栈的提取,持续卡顿场景下的退火处理等。卡顿监控方案的实现过程中,通过融合行业成熟优秀的方案思路,实现了主线程阻塞卡顿的检测能力。卡顿监控能力还在持续进化,后续还有不少可以优化和提升的点,如支持高CPU占用卡顿、启动卡顿等的检测。目前这套方案已经应用在阿里云ARMS用户体验监控iOS SDK中,您可以参考接入文档体验使用。相关问题可以加入“RUM用户体验监控支持群”(钉钉群号:67370002064)进行咨询。

文章大纲

推荐文章

超过 2000+ 位开发者正在阅读

给 OpenClaw 加上企业级 Memory

给 OpenClaw 加上企业级 Memory

4646 阅读

阿里云 STAROps 全域智能运维平台发布!

阿里云 STAROps 全域智能运维平台发布!

3453 阅读

阿里云正式发布 RCA Benchmark

阿里云正式发布 RCA Benchmark

2716 阅读

推荐视频

UModel 最佳实践 Vol.1 UModel 数据建模全景解读

UModel 最佳实践 Vol.1 UModel 数据建模全景解读

3854 观看50:35
云监控2.0全景:可观测范式升级与智能运维蓝图

云监控2.0全景:可观测范式升级与智能运维蓝图

2532 观看41:06

推荐工具

精选可观测领域开发者工具

云监

云监控 2.0 沙箱体验

7483 使用

免费

免费网络拨测工具

2746 使用