文章目录一、CPU 和 GPU 1、CPU2、GPU二、卡顿1、屏幕成像原理2、卡顿产生原因3、卡顿解决思路4、卡顿解决方案三、离屏渲染 1、什么是离屏渲染2、离屏渲染消耗性能3、如何触发离屏渲染4、如何检测离屏渲染四、卡顿检...
文章目录
一、CPU 和 GPU
1、CPU
2、GPU
二、卡顿
1、屏幕成像原理
2、卡顿产生原因
3、卡顿解决思路
4、卡顿解决方案
三、离屏渲染
1、什么是离屏渲染
2、离屏渲染消耗性能
3、如何触发离屏渲染
4、如何检测离屏渲染
四、卡顿检测
一、CPU 和 GPU
在屏幕成像过程中,CPU 和 GPU 起着至关重要的作用
1、CPU(中央处理器)
负责: 对象的创建(alloc)和销毁(
release)、对象属性的调整(
frame/bounds)、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制(Core Graphics)
2、GPU(图像处理器)
纹理的渲染、
其中 : 帧缓存有两块,前帧缓存和后帧缓存。当前帧缓存存满且处于读取状态,就可以用后帧缓存。
二、卡顿
1、屏幕成像原理
当经过 CPU 计算、GPU 渲染到帧缓冲区(buffer)后,屏幕显示器会先发来垂直同步信号(
VSync),代表即将显示一帧数据。当绘制完一行的时候会发出水平同步信号(
HSync)。直到一帧绘制完成。后,继续发出垂直同步信号继续显示下一帧数据。
2、卡顿产生的原因
经过 CPU 计算,GPU 渲染到 缓冲区,发来垂直同步信号,开始成像过程中,由于 CPU 和 GPU 花费时间过长,垂直同步信号来了,当前正在计算的数据就会丢失,也就是丢帧,就会显示上一帧数据。丢帧的数据只能等待下一个垂直同步信号。
3、卡顿解决思路
尽可能减少 CPU、
GPU资源消耗,按照 60 FPS的刷帧率,大概每隔 16 ms 就会有一次
VSync
信号
4、解决方案
4.1 CPU 方面
4.1.1 尽量使用轻量级对象,比如用不到时间处理的地方,可以考虑用 CALayer
替代UIView
4.1.2 不要频繁调用 UIView
的相关属性,例如 : frame、bounds、transform
等属性,减少不必要的修改
提前计算好布局,在有需要的时候一次性调整对应属性,不要多次修改属性
4.1.3 AutoLayout
会比直接设置 frame
消耗更多的CPU
资源
4.1.4 图片的 size
最好刚好跟 UIImageView
的size
保持一直
4.1.5 控制好线程的最大并发量
4.1.5 尽量把耗时操作放到子线程,例如: 文本计算、图片解码、绘制
4.2 GPU 方面
4.2.1 尽量减少视图数量和层次
4.2.2 GPU 能处理的最大纹理的尺寸为 4096*4096
,一旦超过这个尺寸,就会占用 CPU
资源进行处理,所以纹理尽量不要超过这个尺寸
4.2.3 减少透明的视图,不透明就设置 opaque
为 YES
4.2.3 尽量避免出现离屏渲染
三、离屏渲染
1、什么是离屏渲染
在 OpenGL
中, GPU
有两种渲染方式
当前屏幕渲染(On-Screen Rendering
):在当前用于显示的屏幕缓冲区进行渲染操作
离屏渲染(off-Screen Rendering
):在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作
2、离屏渲染消耗性能
需要开辟新的缓冲区(buffer
)
离屏渲染的整个过程,需要多次切换上下文,显示从当前屏幕(on-Screen
)切换到离屏(off-Screen
),等到离屏渲染结束后,将离屏缓冲区的渲染结果显示到屏幕上后,又需要将上下文从从屏切换到当前屏幕。
3、如何触发离屏渲染
光珊化(layer.shouldRasterize = YES
)
遮罩(layer.mask
)
圆角,同时设置 layer.maskToBounds = YES; layer.cornerRadius > 0
,如果非得设置,可以用 CoreGraphics
绘制圆角会用中间挖空的带圆角的图片。
阴影,例如: layer.shadowColor
, 可通过 shadowPath
设置阴影
4、如何检测离屏渲染
点击模拟器,在其菜单栏 点击 Debug
,在其下拉菜单栏选择 Color off-Screen Rendered
如果,存在离屏熏染,对应突然会被高亮成黄色。
四、卡顿检测
平时所说的 "卡顿"主要是因为在主线程执行了比较耗时的操作,那么如何监听哪些操作比较耗时呢?可以添加 Observe
到主线程的 RunLoop
中,通过监听 RunLoop
状态切换的耗时,达到监控卡顿的目的。即结束休眠到处理 souce0
这一过程所消耗的时间。
推荐LXDAppFluecyMonitor
核心代码如下:
#pragma mark - Public
- (void)startMonitoring {
if (_isMonitoring) { return; }
_isMonitoring = YES;
CFRunLoopObserverContext context = {
0,
(__bridge void *)self,
NULL,
NULL
};
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &lxdRunLoopObserverCallback, &context);
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
dispatch_async(lxd_event_monitor_queue(), ^{
while (SHAREDMONITOR.isMonitoring) {
if (SHAREDMONITOR.currentActivity == kCFRunLoopBeforeWaiting) {
__block BOOL timeOut = YES;
dispatch_async(dispatch_get_main_queue(), ^{
timeOut = NO;
dispatch_semaphore_signal(SHAREDMONITOR.eventSemphore);
});
[NSThread sleepForTimeInterval: lxd_time_out_interval];
if (timeOut) {
[LXDBacktraceLogger lxd_logMain];
}
dispatch_wait(SHAREDMONITOR.eventSemphore, DISPATCH_TIME_FOREVER);
}
}
});
dispatch_async(lxd_fluecy_monitor_queue(), ^{
while (SHAREDMONITOR.isMonitoring) {
long waitTime = dispatch_semaphore_wait(self.semphore, dispatch_time(DISPATCH_TIME_NOW, lxd_wait_interval));
if (waitTime != LXD_SEMPHORE_SUCCESS) {
if (!SHAREDMONITOR.observer) {
SHAREDMONITOR.timeOut = 0;
[SHAREDMONITOR stopMonitoring];
continue;
}
if (SHAREDMONITOR.currentActivity == kCFRunLoopBeforeSources || SHAREDMONITOR.currentActivity == kCFRunLoopAfterWaiting) {
if (++SHAREDMONITOR.timeOut < 5) {
continue;
}
[LXDBacktraceLogger lxd_logMain];
[NSThread sleepForTimeInterval: lxd_restore_interval];
}
}
SHAREDMONITOR.timeOut = 0;
}
});
}
本文标题为:iOS 卡顿
基础教程推荐
- Android Compose自定义TextField实现自定义的输入框 2023-05-13
- Android开发Compose集成高德地图实例 2023-06-15
- iOS开发使用XML解析网络数据 2022-11-12
- Android实现短信验证码输入框 2023-04-29
- iOS开发 全机型适配解决方法 2023-01-14
- iOS Crash常规跟踪方法及Bugly集成运用详细介绍 2023-01-18
- IOS获取系统相册中照片的示例代码 2023-01-03
- Flutter进阶之实现动画效果(三) 2022-10-28
- MVVMLight项目Model View结构及全局视图模型注入器 2023-05-07
- iOS中如何判断当前网络环境是2G/3G/4G/5G/WiFi 2023-06-18