App 页面"卡顿“的原因是什么
在我们 iOS 开发的过程中,会遇到 APP 不流畅的情况。在屏幕图像显示的那些事儿中, 讲述了屏幕渲染的流程,当 GPU 或者 CPU 没有在屏幕每一次刷新时完成内容的提交,而造成渲染流水线耗时过长,就会导致重复渲染同一帧数据造成图像掉帧,也就是所谓的”卡顿“。
对"卡顿"的性能检测
FPS监控
为了保持流程的UI交互,App的刷新拼搏应该保持在 60fps
左右,其原因是因为 iOS 设备默认的刷新频率是60次/秒,而1次刷新(即 VSync
信号发出)的间隔是 ,所以如果在 16.67ms 内没有准备好下一帧数据,就会产生”卡顿"。 可以在程序中用
CADisplayLink
来测量帧率),然后在屏幕上显示出来,但应用内的 FPS 显示并不能够完全真实测量出性能,因为它仅仅测出应用内的帧率。
推荐 YYFPSLabel
主线程卡顿监控
通过子线程监测主线程的 RunLoop
,判断两个状态(kCFRunLoopBeforeSources
和 kCFRunLoopAfterWaiting
)之间的耗时是否达到一定阈值
推荐 matrix
怎么尽量避免"卡顿"的现象呢?
页面布局优化
-
预排版
如果你的视图层级过于复杂,当视图呈现或者修改的时候,计算图层帧率就会消耗一部分时间。当必要的时候可以使用预排版,预排版主要针对频繁变更布局的场景,例如UITableView
,通过提前在自定义线程计算视图布局,利用多核的优势,减少在主线程的计算,对 CPU 进行减负,从而加快渲染的速度。
预排版流程
-
AutoLayout
苹果在 iOS 6系统时就引入了 AutoLayout,在这之后 视图嵌套的数量对性能的影响一直是呈指数级增长的。不过,在 WWDC 220 Session High Performance Auto Layout中苹果提到了 iOS 12 将大幅提高 “Auto Layout 性能,使滑动达到满帧".Auto Layout 在 iOS 12 中优化后的表现。
AutoLayou 在 iOS 12 优化后的表现
可以看到,优化后的性能,已经基本和手写布局一样可以达到性能随着视图嵌套的数量呈线性增长了。因此,如果 App 是兼容 iOS 12 以前的话,针对复杂视图还是尽量使用代码布局。
图层优化
- 太多的图层就会引起 CPU 的瓶颈,减少图层的个数。
- 避免大量的离屏渲染。
- 由于
View
负责交互,Layer
负责渲染,因此在无需触摸交互的场景下,尽可能的使用Layer
进行绘制。 - 在有大量静态的文本展示时,可以优先使用
CoreText
或者CATextLayer
, 并将其作为一个子图层。 - 光栅化可以提供渲染性能,但是要避免使用在经常变更的图层.
- 尽可能减少混合透明图层。
- 如果正在使用多个
UILabel
或者UIImageView
实例去显示固定内容,可以把他们全部替换成一个单独的视图,然后用-drawRect:
方法绘制出那些复杂的视图层级。。 或者使用 使用CALayer
的-renderInContext:
方法,可以将图层及其子图层快照进一个Core Graphics
上下文然后得到一个图片,它可以直接显示在UIImageView
中,或者作为另一个图层的contents
。 注:可以参考美团的Graver。
图片处理优化
-
一旦图片文件被加载就必须要进行解码,解码过程是一个相当复杂的任务,需要消耗非常长的时间。解码后的图片将同样使用相当大的内存。当图片过大时,就会占用CPU更多的时间在图层每次显示之前对图片预处理,同样也会降低性能。因此,尽可能减少大图片或者进行预解码,在子线程先将图片绘制到
CGBitmapContext
,然后从BitMap
创建图片进行预解码,在主线程进行渲染。 -
尽可能使用 PNG 图片,不使用 JPGE 图片,PNG图片 使用的无损压缩算法可以比使用 JPEG 的图片做到更快地解压
-
尽可能使得图片资源为指定尺寸,避免消耗 CPU 性能。
-
如果有很多张图片要显示,最好不要提前把所有都加载进来,而是应该当移出屏幕之后立刻销毁。通过选择性的缓存,这样就可以避免来回滚动时图片重复性的加载
-
在
TableView
和ColletionView
中,我们可以利用UIScrollViewDeleatge
的委托方法实现对图片的按需加载或者使用较小分辨率的图片,避免滚动过程中图片的无用加载。- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
异步渲染
UIKit
的单线程天性意味着 UI 要在主线程上更新,这意味着绘制会打断用户交互,甚至让整个 App 看起来处于无响应状态。但是如果能避免用户等待绘制完成就好多了。 针对这个问题,有一些方法可以用到:例如异步渲染,Core Animation
提供了 drawsAsynchronously
属性。
drawsAsynchronously
属性对传入 -drawLayer:inContext:的CGContext
进行改动,允许 CGContext
延缓绘制命令的执行以至于不阻塞用户交互。它自己的 -drawLayer:inContext:
方法只会在主线程调用,但是 CGContext
并不等待每个绘制命令的结束。相反地,它会将命令加入队列,当方法返回时,在后台线程逐个执行真正的绘制。 根据苹果的说法。这个特性在需要频繁重绘的视图上效果最好(比如我们的绘图应用,或者诸如 UITableViewCell
之类的),对那些只绘制一次或很少重绘的图层内容来说没什么太大的帮助。
Graver(已下架) 、Async(原名 AsyncDisplayKit)、 YYAsyncLayer 异步渲染三方库
网友评论