当前位置: 首页 > news >正文

页面卡顿检测方案

引言

卡顿现象在早期的开发项目中是一个非常值得注意的问题。随着应用功能的不断增加,代码复杂度也在不断提升,特别是在较为低端的机型上,稍有不慎就可能引发卡顿现象。虽然近年来新发布的设备性能显著提升,但卡顿问题仍然不容忽视。即使在高端设备上,复杂的动画、数据处理任务或不合理的资源分配,都会导致用户体验的显著下降。

卡顿不仅影响用户的使用感受,还可能导致用户对应用的不满,进而降低应用的留存率。因此,如何有效地检测、分析和解决卡顿问题,成为了 iOS 开发中一个至关重要的环节。尤其是在开发的早期阶段,通过及时发现和解决卡顿问题,能够有效提升应用的性能表现,并为后续的优化工作打下坚实的基础。

在这篇文章中,我们将深入探讨 iOS 应用中的卡顿检测方法,介绍一些常用的工具和技术手段,帮助开发者在开发过程中及早发现潜在的性能瓶颈,确保应用在各类设备上都能保持流畅的用户体验。

什么是卡顿?

什么是帧率 (Frames Per Second, FPS): 帧率就是每秒中显示的画面帧数,通常来说,iOS设备的屏幕刷新帧率是60Hz,也就是说理想情况下每秒应该现实60帧画面。

帧率与流畅度的关系:如果页面每秒的帧数低于60帧比如20帧或者更低,那么用户就会感到卡顿,帧率越低,卡顿越明显。

iOS应用所有的UI渲染工作都是在主线程上进行的,但是主线程不仅仅负责UI渲染,还需要处理用户交互,动画,网络请求数据处理等任务,如果有些复杂任务阻塞的主线程,那么就会导致帧率下降,进而引发卡顿。

卡顿产生原因

当我们在主线程上进行比较复杂的计算和数据处理时会严重影响帧率。当一次性加载过多或者过大的图片时,尤其是在滑动过程中进行加载容易导致UI卡顿。

在项目开发中比较常见的场景,当一个页面中有多个列表需要加载和显示时。或者当我们使用UIScrollView加载过多的图片时,由于没有复用机制这些图片会被同时渲染从而阻塞主线程。

卡顿检测指标

卡顿检测最显而易见的两个指标一个是帧率,当页面渲染的帧率没有达到流程的水准我们就可以定义为它当前发生了卡顿。

另一个指标是主线程的阻塞时间,主线程被阻塞的时间长度直接影响UI的响应速度,如果主线程阻塞时间过程,用户操作就会感觉到迟钝或者卡顿。

卡顿检测方案

基于上面两个指标我们来实现两个不同的卡顿检测方案。

使用CADisplayLink检测帧率

CADisplayLink的用法和定时器相似,它会以屏幕的刷新频率调用制定的目标方法,所以我们可以通过计算1秒内目标方法的执行次数来计算FPS,当FPS低于我们约定值时可以判断发生卡顿。

具体代码如下:

class DLFrameRateMonitor: NSObject {/// CADisplayLinkprivate var displayLink:CADisplayLink?/// 上一次时间private var lastTimestamp:CFTimeInterval = 0/// 帧数private var frameCount:Int = 0/// 开始检测func startMonitor() {displayLink = CADisplayLink(target: self, selector: #selector(displayLinkAction))displayLink?.add(to: RunLoop.main, forMode: .common)lastTimestamp = 0frameCount = 0}/// 结束检测func stopMonitor() {displayLink?.invalidate()displayLink = nil}@objc private func displayLinkAction() {if lastTimestamp == 0 {lastTimestamp = displayLink?.timestamp ?? 0return}frameCount += 1let delta = displayLink?.timestamp ?? 0 - lastTimestampif delta < 1 {return}let fps = Double(frameCount) / deltaprint("FPS: \(fps)")lastTimestamp = displayLink?.timestamp ?? 0frameCount = 0}
}

使用主线程阻塞检测卡顿

该方案的核心私信就是通过检测主线程的执行时间来判断是否发生了看现象。

通过创建一个子线程,监测主线程的“RunLoop”状态,创建一个“CFRunLoopObserver”监测主线程“RunLoop”的执行状态,记录它的进入和退出时间,如果这个时间差超过一定的的阈值,那么我们就认为它发生了卡顿。具体代码如下:

注册通知:

    override init() {super.init()// 注册runloop状态改变通知let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.beforeSources.rawValue | CFRunLoopActivity.afterWaiting.rawValue, true, 0) { (observer, activity) inself.runloopActivityChange(activity: activity)}CFRunLoopAddObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)}
    /// runloop状态改变private func runloopActivityChange(activity:CFRunLoopActivity) {self.activity = activitysemaphore.signal()}

开始检测:

    /// 开始检测func startMonitor() {if isMonitoring {return}isMonitoring = truemonitorQueue.async {while self.isMonitoring {let timeout = DispatchTime.now() + .seconds(1)let result = self.semaphore.wait(timeout: timeout)if result == .timedOut {if self.activity == .beforeSources || self.activity == .afterWaiting {self.timeoutCount += 1if self.timeoutCount >= 3 {print("卡顿了")}}}else {self.timeoutCount = 0}}}}

结束检测:

    /// 结束检测func stopMonitor() {isMonitoring = falsesemaphore.signal()}

结语

在现代应用开发中,用户体验至关重要,而流畅的界面响应是确保良好用户体验的关键之一。主线程卡顿,无论是由于复杂的计算任务、长时间的 I/O 操作,还是不必要的 UI 更新,都会直接影响应用的流畅度和响应速度。因此,及时检测和解决卡顿问题,是提升应用性能和用户满意度的重要步骤。

在本篇文章中,我们探讨了几种主线程卡顿检测方案,包括 CADisplayLink 和基于主线程运行时状态的监测方法。CADisplayLink 作为一种高精度的解决方案,能够实时检测帧率的波动,从而发现卡顿现象。它的优势在于能提供详细的帧渲染信息,帮助我们精准定位性能瓶颈。然而,使用 CADisplayLink 的方法也有其局限性,例如可能对系统性能产生额外的负担。

另一方面,通过结合 RunLoop 状态和 GCD,我们可以实现更灵活的卡顿检测方案。这些方法能够利用系统提供的机制,实时监控主线程的状态和响应时间,帮助我们更好地识别潜在的性能问题。通过信号量和子线程监测,我们可以避免主线程因卡顿问题导致的用户体验下降。

每种检测方案都有其优缺点,选择合适的方法需要根据应用的具体需求和性能目标进行权衡。通过有效地应用这些检测技术,我们可以及早发现和修复卡顿问题,从而优化应用性能,提升用户体验。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【STL】红黑树的全面探索与红黑树的实现
  • SQL SERVER日常表碎片和统计信息优化脚本
  • 构建Dubbo工程详解
  • Android Studio Koala下载并安装,测试helloworld.
  • 力扣--1657.确定两个字符串是否接近
  • 氛围感视频素材高级感的去哪里找啊?带氛围感的素材网站库分享
  • 力扣45.跳跃游戏II
  • 通过ICMP判断网络故障
  • Qt:鼠标事件
  • 最近公共祖先(LCA),树上差分,树的直径总结
  • Python优化算法12——蝴蝶优化算法(BOA)
  • vscode解决运行程序无法从控制台输入问题
  • vue的vue.config.js中反向代理pathRewite的理解
  • html2canvas ios慎用和createImageBitmap ios慎用
  • 12、stm32通过dht11读取温湿度
  • 【知识碎片】第三方登录弹窗效果
  • 2019.2.20 c++ 知识梳理
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • CSS 三角实现
  • gulp 教程
  • JS字符串转数字方法总结
  • Material Design
  • React中的“虫洞”——Context
  • Spring Cloud中负载均衡器概览
  • vue-router 实现分析
  • webpack+react项目初体验——记录我的webpack环境配置
  • zookeeper系列(七)实战分布式命名服务
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 分布式任务队列Celery
  • 工作中总结前端开发流程--vue项目
  • 关于 Cirru Editor 存储格式
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 前端性能优化--懒加载和预加载
  • 少走弯路,给Java 1~5 年程序员的建议
  • 推荐一个React的管理后台框架
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​马来语翻译中文去哪比较好?
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • (+4)2.2UML建模图
  • (7)svelte 教程: Props(属性)
  • (Python) SOAP Web Service (HTTP POST)
  • (ros//EnvironmentVariables)ros环境变量
  • (三)模仿学习-Action数据的模仿
  • (十三)Flask之特殊装饰器详解
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解
  • (一)SvelteKit教程:hello world
  • (一)基于IDEA的JAVA基础10
  • ****Linux下Mysql的安装和配置
  • *p++,*(p++),*++p,(*p)++区别?
  • .NET Standard 支持的 .NET Framework 和 .NET Core
  • .NET 反射 Reflect
  • .NET6 命令行启动及发布单个Exe文件