iOS卡顿原因与优化

iOS卡顿原因与优化

1. 卡顿简介

卡顿: 指用户在使用过程中出现了一段时间的阻塞,使得用户在这一段时间内无法进行操作,屏幕上的内容也没有任何的变化。

卡顿作为App的重要性能指标,不仅影响着用户体验,更关系到用户留存、DAU等重要产品数据。因此,需要关注APP的卡顿

2. 卡顿产生的原因

首先,屏幕上看到的所有内容都是计算机绘制出来的图像

帧率:Frames Per Second(fps),表示每秒渲染帧数,

通常用于衡量画面的流畅度,每秒帧数越多,则表示画面越流畅。

通常,60fps比较流畅,也就是60张/秒,每张图片需要的渲染时间大约是:
1s/60张 = 1000ms/60张 = 16.7ms/1张

也就是1张图像在16.7ms内出现一次,就不会造成卡顿现象。

  • CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等
  • GPU 进行变换、合成、渲染,把渲染结果提交到帧缓冲区去,在下一次 VSync 信号到来时显示到屏幕上

卡顿产生的原因:当单位时间内,界面要刷新的时候,CPU或GPU由于计算量大,没有做好准备,

就会造成界面显示前一个时间段的界面,从而造成卡顿、掉帧现象

3. 如何避免卡顿

核心: 减少CPU、GPU的资源消耗

CPU

  1. 创建对象:对象的创建会分配内存、调整属性,因此,尽量使用轻量级的对象
  2. 布局计算:视图布局的计算是 App 中最为常见的消耗 CPU 资源的地方,不要频繁的调用UIView的相关属性;尽量提前计算好布局,在有需要时一次性调整到对应的属性,不要多次、频繁的计算和调整这些属性
  3. 线程处理:控制一下线程的最大并发数量;尽量把耗时的操作放到子线程,包括:文本计算、布局计算、图片的解码编码

GPU

  • 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
  • GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU的资源进行处理,所以纹理尺寸尽量不要超过这个尺寸
  • 尽量减少视图数量和层次
  • 减少透明的视图(alpha<1),不透明的就设置opaque为YES
  • 尽量避免出现离屏渲染

在OpenGL中,GPU有2种渲染方式:

1 当前屏幕渲染:在当前用于显示的屏幕缓冲区进行渲染操作

2 离屏渲染:在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作

离屏渲染消耗性能的原因?
  • 需要创建新的缓冲区
  • 离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕
哪些操作会触发离屏渲染?
  • 光栅化,layer.shouldRasterize = YES
  • 遮罩,layer.mask
  • 圆角,同时设置layer.masksToBounds = YES、layer.cornerRadius大于0考虑通过CoreGraphics绘制裁剪圆角,或者叫美工提供圆角图片
  • 阴影,layer.shadowXXX,如果设置了layer.shadowPath就不会产生离屏渲染

4. 卡顿检测

  • 监控FPS
  • 监控RunLoop
  • ping主线程

4.1 监控FPS

帧率:Frames Per Second(fps),表示每秒渲染帧数

使用系统CADisplayLink监控,CADisplayLink是一个与屏幕刷新率相同的定时器,大约1/60s调用一次。

将其注册到RunLoop里面,计算当前画面的帧数。

delta为时间差等于1

4.2 监控RunLoop

由于UI刷新只能在主线程操作,因此,平时所说的"卡顿",主要是因为主线程执行了比较耗时的操作

因此,可以添加observer到主线程Runloop中,通过监听Runloop状态切换的耗时,以达到监控卡顿的目的

RunLoop在BeforeSources和AfterWaiting后会进行任务的处理。可以在此时阻塞监控线程并设置超时时间,若超时后RunLoop的状态仍为RunLoopBeforeSources或AfterWaiting,表明此时RunLoop仍然在处理任务,主线程发生了卡顿

4.3 子线程Ping主线程

ping是常用的网络测试工具,用来测试数据包能否到达ip地址。

ping主线程的核心思想是向主线程发送一个信号,一定时间内收到了主线程的回复,即表示当前主线程流畅运行。

没有收到主线程的回复,即表示当前主线程在做耗时运算,发生了卡顿。

子线程Ping主线程的实现思路:

  1. 创建一个子线程进行循环检测,每次检测时设置标记位为YES
  2. 然后派发任务到主线程中将标记位设置为NO。
  3. 接着子线程休眠设定的阈值,判断标志位是否成功设置成NO,如果没有说明主线程发生了卡顿。

相当于:

子线程设置一个标识YES

如果发生了主线程的卡顿,那么到规定时间,主线程内的代码没有执行,则标识还是YES,这时候,代表卡顿

如果没有发生卡顿,那么到规定时间,主线程内的代码执行,则表示变为NO,这时候,代表没有发生卡顿

总结

相关推荐
坏小虎2 小时前
Expo 快速创建 Android/iOS 应用开发指南
android·ios·rn·expo
光影少年3 小时前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
北京自在科技3 小时前
Find My 修复定位 BUG,AirTag 安全再升级
ios·findmy·airtag
Digitally4 小时前
如何不用 USB 线将 iPhone 照片传到电脑?
ios·电脑·iphone
Sim148016 小时前
iPhone将内置本地大模型,手机端AI实现0 token成本时代来临?
人工智能·ios·智能手机·iphone
Digitally18 小时前
如何将 iPad 上的照片传输到 U 盘(4 种解决方案)
ios·ipad
报错小能手21 小时前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
LcGero21 小时前
Cocos Creator 业务与原生通信详解
android·ios·cocos creator·游戏开发·jsb
ii_best21 小时前
lua语言开发脚本基础、mql命令库开发、安卓/ios基础开发教程,按键精灵新手工具
android·ios·自动化·编辑器
用户223586218202 天前
WebKit WebPage API 的引入尝试与自研实现
ios