iOS界面优化 ——卡顿检测和优化方案

  • 1. 卡顿原理
  • 2. 卡顿检测
  • 3. 界面优化


1. 卡顿原理

卡顿,也就是掉帧。当UIView被绘制时,cpu执行drawrect,通过context将数据写入backingstore
当backingstore 写完后,通过 render server 交给gpu去渲染,渲染的东西放到framebuffer里面,然后通过Video Controller交给monitor进行显示。说到底cpu就是做绘制的操作把内容放到缓存里,gpu负责从缓存里读取数据然后渲染到屏幕上。

最开始时,FrameBuffer只有一个,这种情况下FrameBuffer的读取和刷新有很大的效率问题,如果gpu卡在那边,那么FrameBuffer接受不到消息,就会闲置。为了解决这个问题,引入了双缓存区。即双缓冲机制。在这种情况下,GPU会预先渲染好一帧放入FrameBuffer,让视频控制器读取,当下一帧渲染好后,GPU会直接将视频控制器的指针指向第二个FrameBuffer。这时候,如果中间有的帧在一定时间内没有渲染完,那么就会被丢弃掉,转而去渲染下一个帧。这时候,就会出现卡顿。

这里采用了垂直同步信号机制来进行帧和帧的切换。当开启垂直同步后,GPU会等待显示器的VSync信号发出后,才进行新的一帧渲染和FrameBuffer更新。而目前iOS设备中采用的正是双缓存区+VSync

2. 卡顿检测

这里推荐三种卡顿监控的方案:

  • FPS监控:为了保持流程的UI交互,App的刷新应该保持在60fps左右,其原因是因为iOS设备默认的刷新频率是60次/秒,而1次刷新(即VSync信号发出)的间隔是 1000ms/60 = 16.67ms,所以如果在16.67ms内没有准备好下一帧数据,就会产生卡顿。主要通过CADIsplayLink实现。参照YYKit中的YYFPSLabel,借助link的时间差,来计算一次刷新刷新所需的时间,然后通过 刷新次数 / 时间差 得到刷新频次,并判断是否其范围,通过显示不同的文字颜色来表示卡顿严重程度。
  • 主线程卡顿监控:通过子线程监测主线程的RunLoop,判断两个状态(kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting)之间的耗时是否达到一定阈值
  • 第三方工具:OC:微信卡顿检测工具matrix,滴滴卡顿方案DoraemonKit,Swift:ANREye

通过注释可以得到CADisplayLink是一个时间长度为vsync的timer。

3. 界面优化

  • 预排版:尽量提前计算视图布局,例如提前算出cell的行高,缓存cell的行高来避免多次计算。
  • 预解码 & 预渲染
    图片加载流程:
    图片要显示,就要加载一个UIImage,UIImage是一个模型,里面包含Data BufferimageBuffer,然后由Controller控制UIImage显示在UIImageView上面的。
  • ios 卡顿 ios卡顿怎么解决_缓存

  • 其中Data Buffer 进行解码然后缓存到imageBuffer里面,然后才可以由Frame Buffer进行渲染。
  • ios 卡顿 ios卡顿怎么解决_ios_02

  • 在SDWebImage里面就有预解码的步骤:
  • ios 卡顿 ios卡顿怎么解决_iOS_03

  • 按需加载,例如在TableView中滑动时不加载图片,使用默认占位图,而是在滑动停止时加载
  • 异步渲染
  1. UIView 和 CALayer都是 UI 操作的对象
  2. UIView是 CALayer用于交互的对象,UIView是CALayer的delegate ,UIView是UIResponder的子类,其中提供了很多CALayer所没有的交互接口,主要负责处理用户触发的各种操作,给CALayer 提供内容,
  3. CALayer主要负责绘制,在图像和动画上渲染性能更好;

异步渲染的原理其实也就是在子线程将所有的视图绘制成一张位图,然后回到主线程赋值给 layer的 contents,Graver框架的异步渲染流程如下:

ios 卡顿 ios卡顿怎么解决_iOS_04


最后会得到一张图,这样也减少了图层的层级。

ios 卡顿 ios卡顿怎么解决_缓存_05

  • 尽量避免使用透明view,因为使用透明view,会导致在GPU中计算像素时,会将透明view下层图层的像素也计算进来,即颜色混合处理,
  • 少使用addView 给cell动态添加view
  • 尽量用轻量级的对象代替重量级的对象,可以对性能有所优化,例如 不需要相应触摸事件的控件,用CALayer代替UIView
  • 尽量减少对UIView和CALayer的属性修改
  • CALayer内部并没有属性,当调用属性方法时,其内部是通过运行时resolveInstanceMethod为对象临时添加一个方法,并将对应属性值保存在内部的一个Dictionary中,同时还会通知delegate、创建动画等,非常耗时
  • UIView相关的显示属性,例如frame、bounds、transform等,实际上都是从CALayer映射来的,对其进行调整时,消耗的资源比一般属性要大
  • 当有大量对象释放时,也是非常耗时的,尽量挪到后台线程去释放
  • 减少图层的层级
  • 减少离屏渲染