iOS性能---滑动卡顿优化(1)汇总篇

概念

  • FPS:表示每秒渲染的帧数,通过用于衡量画面的流畅度,数值越高则表示画面越流畅。
  • CPU:负责对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制(Core Graphics)。
  • GPU: 负责纹理的渲染(将数据渲染到屏幕)。
  • 垂直同步技术: 让CPU和GPU在收到vSync信号后再开始准备数据,防止撕裂感和跳帧,通俗来讲就是保证每秒输出的帧数不高于屏幕显示的帧数。
  • 双缓冲技术iOS是双缓冲机制,前帧缓存和后帧缓存,cpu计算完GPU渲染后放入缓冲区中,当gpu下一帧已经渲染完放入缓冲区,且视频控制器已经读完前帧,GPU会等待vSync(垂直同步信号)信号发出后,瞬间切换前后帧缓存,并让cpu开始准备下一帧数据。

滑动卡顿的原因

  • 在 VSync 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU 会将计算好的内容提交到 GPU 去,由 GPU 进行变换、合成、渲染。随后 GPU 会把渲染结果提交到帧缓冲区去,等待下一次 VSync 信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync 时间内,CPU 或者 GPU 没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。

卡顿监测

  1. 监测fps,具体见YYFpsLabel,利用CADisplayLink统计delta(>=1)秒内的执行次数count,然后用count/delta得到fps,局部代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     //代码在 YYKitDemo -> other -> YYFpsLabel
    //记录CADisplayLink的方法调用
    - (void)tick:(CADisplayLink *)link {
    _count++; //统计次数
    NSTimeInterval delta = link.timestamp - _lastTime; //距离上次统计的时间差
    if (delta < 1) return; //时间差不到一秒不能计算
    float fps = _count / delta; //计算出fps
    _lastTime = link.timestamp; //最后时间重置
    _count = 0; //次数归零
    }
  2. 基于runloop,具体使用微信团队的matrix
    原理:

    Matrix卡顿监控在 Runloop 的起始最开始和结束最末尾位置添加 Observer,从而获得主线程的开始结束状态。卡顿监控起一个子线程定时检查主线程的状态,当主线程的状态运行超过一定阈值则认为主线程卡顿,从而标记为一个卡顿。同时,我们也认为 CPU 过高也可能导致应用出现卡顿,所以在子线程检查主线程状态的同时,如果检测到 CPU 占用过高,会捕获当前的线程快照保存到文件中。目前微信应用中认为,单核 CPU 的占用超过了 80%,此时的 CPU 占用就过高了。

  3. 基于runloop,使用LXDAppFluecyMonitor项目

  4. Instruments Time Profiler -> Call Tree Options :

    • Separete By Thread :按线程划分
    • Invert Call Tree :逆向调用树,方便查看调用顺序
    • Hide System Libraries:隐藏系统库

滑动卡顿优化方案

  • CPU 资源消耗原因和解决方案

    1. 尽量不要动态创建销毁对象,最好复用对象,因为对象创建分配内存比较消耗CPU资源。
    2. 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属
    3. 如果需要手写代码布局,不用Autolayout
    4. 尽量把耗时的操作放到子线程,文本处理(尺寸计算、绘制),图片处理(异步解码、异步绘制)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      - (void)display {
      dispatch_async(backgroundQueue, ^{
      CGContextRef ctx = CGBitmapContextCreate(...);
      // draw in context...
      CGImageRef img = CGBitmapContextCreateImage(ctx);
      CFRelease(ctx);
      dispatch_async(mainQueue, ^{
      layer.contents = img;
      });
      });
      }
    5. 预排版,尽量手动布局,在子线程,将model用一个对象layout包装,根据model计算出布局信息(如组件frame、cell高度、富文本信息等)存入对象layout,然后主线程刷新传给cell
  • GPU 资源消耗原因和解决方案

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

AsyncDisplayKit

  • AsyncDisplayKit源码
  • AsyncDisplayKit 是 Facebook 开源的一个用于保持 iOS 界面流畅的库,
  • SDK 认为,阻塞主线程的任务,主要分为上面这三大类。文本和布局的计算、渲染、解码、绘制都可以通过各种方式异步执行,但 UIKit 和 Core Animation 相关操作必需在主线程进行。ASDK 的目标,就是尽量把这些任务从主线程挪走,而挪不走的,就尽量优化性能。
  • ASDK 把大量常用控件都封装成了 ASNode 的子类,比如 Button、Control、Cell、Image、ImageView、Text、TableView、CollectionView 等

参考文档

Matrix-iOS 卡顿监控
matrix项目
YYKit项目
LXDAppFluecyMonitor项目
iOS卡顿监测方案总结
iOS 保持界面流畅的技巧