iOS 卡顿

文章目录一、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最好刚好跟 UIImageViewsize保持一直

4.1.5 控制好线程的最大并发量

4.1.5 尽量把耗时操作放到子线程,例如: 文本计算、图片解码、绘制

4.2 GPU 方面

4.2.1 尽量减少视图数量和层次

4.2.2 GPU 能处理的最大纹理的尺寸为 4096*4096,一旦超过这个尺寸,就会占用 CPU资源进行处理,所以纹理尽量不要超过这个尺寸

4.2.3 减少透明的视图,不透明就设置 opaqueYES

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 卡顿

基础教程推荐