日志9. iOS性能优化 - 卡顿优化

440 阅读3分钟

成像过程:

  • 在iOS中是双缓冲机制,有前帧缓存、后帧缓存

成像原理:

  • 发出垂直同步信号,告诉屏幕即将显示一帧数据;

  • 发出水平同步信号,显示一行一行的数据,直到填充到整个屏幕为止。

产生卡顿的原因

  • 如图:垂直同步信号的发射频率是固定的,信号发出,代表着即将显示数据;

  • 如果期间,CPU或GPU有一步耗时较长(

    第3帧的渲染

    ),垂直信号已发出,但是GPU还没有渲染完,那么就是显示渲染好的

    第2帧

    数据,连续显示相同的帧,就造成了画面卡顿。

  • 第3帧

    会在第4次同步信号过来时再显示。

所以解决卡顿的主要思路:

  • 尽可能减少CPU、GPU的资源消耗。(保证刷帧率60FPS.)

-

-

CPU优化:

  • 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView;

  • 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改;

  • 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性;

  • Autolayout会比直接设置frame消耗更多的CPU资源;

  • 图片的size最好刚好跟UIImageView的size保持一致;

  • 控制一下线程的最大并发数量;

  • 尽量把耗时的操作放到子线程:

    1. 图片处理(解码、绘制);
    2. 文本处理(尺寸计算、绘制):

    // 文字计算 [@"text" boundingRectWithSize:CGSizeMake(100, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:nil context:nil];

    // 文字绘制 [@"text" drawWithRect:CGRectMake(0, 0, 100, 100) options:NSStringDrawingUsesLineFragmentOrigin attributes:nil context:nil];

GPU优化:

  • 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示;

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

  • 尽量减少视图数量和层次;

  • 减少透明的视图(alpha<1),不透明的就设置opaque为YES;

  • 尽量避免出现离屏渲。

卡顿检测

思路:

  • 平时所说的“卡顿”主要是因为在主线程执行了比较耗时的操作;

  • 可以添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控卡顿的目的。
    如果RunLoop的线程,进入睡眠前方法的执行时间过长而导致无法进入睡眠,或者线程唤醒后接受消息时间过长而无法进入下一步的话,就可以认为是线程受阻了。如果这个线程是主线程的话,表现就是出现卡顿。

    一旦发现进入睡眠前的kCFRunLoopBeforeSources状态,或者唤醒后的状态kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,即可认定为卡顿。

    开启子线程监控的代码如下:

    //创建子线程监控
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //子线程开启一个持续的 loop 用来进行监控
        while (YES) {
            //semaphoreWait 信号等待的时间
            long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
            if (semaphoreWait != 0) {
                if (!runLoopObserver) {
                    timeoutCount = 0;
                    dispatchSemaphore = 0;
                    runLoopActivity = 0;
                    return;
                }
                //BeforeSources 和 AfterWaiting 这两个状态能够检测到是否卡顿
                if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                    //将堆栈信息上报服务器的代码放到这里
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
        }// end while
    });
    复制代码
    
  • 第三方:LXDAppMonitor