界面卡顿

界面卡顿_第1张图片

记单词

  • FPS:Frames Per Second每秒传输帧数。60满帧。每16ms刷新一次屏幕。

  • VSync:vertical synchronization垂直同步信号,当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号

  • CPU:操作对象(分内存、调属性、读数据、layer层属性巨耗性能);计算布局(本质调整frame/bounds/center属性);文本计算(NSAttributedString、CoreText);文本渲染(创建CoreText对象、内容异步绘制成Bitmap、默认都是主线程);解码图片(UIImage-> Bitmap->显示。变主线程为异步);绘制图像(异步将内容填充到上下文)。经常的CPU计算量过大就例如当你正在执行一个for循环的时候去滚动TableView这样必须卡顿呀!

  • GPU:CPU将计算好的内容移交给GPU --> 图片纹理+矢量图形 -> 变换+合成+渲染 --> 渲染结果提交到帧缓冲区 --> 显示器发出VSync信号 --> 逐行读取帧缓存区数据 --> 数模传输给显示器显示

离屏渲染负担过重

  • Cell视图大量使用view.layer.cornerRadius + masksToBounds的设置,造成卡顿
  • View圆角遮罩->GPU离屏渲染->拖慢了满帧60fp/s绘制->CPU圆角路径贝塞尔曲线UIBezierPath->UIImage天然圆角->直接填充到方的View

  • 性能 = GPU效率+CPU效率。

  • 离屏渲染真正的消耗 ≠ GPU的图形计算 = 屏幕内外缓冲区来回切换

  • 渲染本质 = 每1/60秒从屏幕缓冲区获取一次渲染结果 = CPU计算和GPU渲染之间及时交换数据 = 顺畅绘制(否则掉帧卡顿)

  • CPU圆角路径 -> 主线程计算耗时卡顿UI -> GPU图形遮罩切换缓冲区又耗内存 -> CPU异步计算UIImage圆角路径 -> 后台线程计算主线程统一绘制 -> 完善的线程管理机制+辅助Cache缓存机制->于是facebook开源的 AsyncDisplayKit诞生了

离屏渲染

  • 原理:OPENGL单独创建内存->当前屏幕缓冲区以外新开辟一个缓冲区->GPU预合成图层混合体->CPU切换到当前屏幕缓冲区上下文->耗性能

  • 触发:shouldRasterize(光栅化保存到bitmap)、masks(遮罩)、shadows(阴影)、edge antialiasing(抗锯齿)、group opacity(不透明)

  • 特例:CPU渲染。使用Core Graphics技术对UIImage进行绘制操作。渲染得到的bitmap最后再交由GPU用于显示。

  • 工具:Instruments的Core Animation工具

负载均衡

  • GPU天生图形处理,擅长浮点运算矩阵运算,但是屏幕渲染缓冲区切换相当耗性能

  • GPU离屏渲染负担过重,一部分运算交给CPU,减少卡顿

  • 离屏渲染的数量比较少,CPU闲着,把运算交给不擅长图形计算的CPU处理弄巧成拙

/**
 *  返回一个图片圆形裁剪
 */
+ (UIImage *)imageWithClipImage:(UIImage *)image borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)color{
    // 图片的宽度和高度
    CGFloat imageWH = image.size.width;
    // 设置圆环的宽度
    CGFloat border = borderWidth;
    // 圆形的宽度和高度
    CGFloat ovalWH = imageWH + 2 * border;
    // 1.开启上下文
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(ovalWH, ovalWH), NO, 0);
    // 2.画大圆
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, ovalWH, ovalWH)];
    [color set];
    [path fill];
    // 3.设置裁剪区域
    UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(border, border, imageWH, imageWH)];
    [clipPath addClip];
    // 4.绘制图片
    [image drawAtPoint:CGPointMake(border, border)];
    // 5.获取图片
    UIImage *clipImage = UIGraphicsGetImageFromCurrentImageContext();
    // 6.关闭上下文
    UIGraphicsEndImageContext();
    
    return clipImage;
}

图片过大过多

  • 过大:tableView滚动 -> Cell子视图内容刷新 - > 网络请求图片 - > Image和View大小不一致 - > 设置.contentMode属性图片自由压缩 -> 图片开始transform计算乘以一个变换矩阵 - > CPU计算陡增 -> 解决方法是网络图片先异步处理后显示Or服务器返回预处理过的大小图

  • 过多:图片网络库下载图片 -> 异步把图片绘制到CGBitmapContext -> 绑定Bitmap到GPU Texture -> GPU调整渲染Texture纹理 - > GPU将渲染好的Bitmap从内存移植到显存 - > 短时间显示大量图片 - > GPU压力陡增 - > 掉帧

动态高度计算

  • 尽量避免临时根据Model数据计算cell高度

  • 尽量不使用Cell高度返回函数,Cell高度委托里尽量从缓存里获取高度

  • 当然Cell定高优先考虑使用tableView.rowHeight属性

  • 缓存一切可缓存!空间换时间

我们获取动态高度的时候,常见逻辑获取Cell高度-->创建Cell-->显示,那么我们通常都需要在计算Cell高度回调方法里先把Cell创建出来,必须先要知道Cell的内容才能决定Cell的高度,通常的做法都是专门谓词创建一个工具Cell,不为别的,就为了把内容复制到Cell的子视图上,如此一来适配Cell的高度,问题在于,在计算Cell高度成功,又会进入创建Cell的回调方法里,正式创建Cell显示出来,那么问题来了,这样本质上我们其实创建了两次Cell,赋值也赋值了两次!我们就会想了,为什么必须先调用Cell高度后调创建Cell的方法呢,如果先创建Cell后获取Cell高度,直接就可以通过Cell的内容布局来显示Cell呀。于是苹果想到了一个方法就是预估Cell高度的方法,就可以实现先创建Cell后返回Cell的真实高度了,如此一来,直接通过创建完成的Cell去获取Cell高度,直接就可以避免多次创建Cell赋值Cell内容的性能消耗了,这对于数量巨大的Cell和内容巨大的性能节省尤其明显。增加Cell盖度估算方法之后的逻辑:获取Cell数量-->数量*估算高度=contentSize--->创建Cell --->布局Cell的内容视图存储真实高度---->直接在返回真实高度里面把数组里面存储的高度返回。原本就是有多少个Cell就调用多少次真实高度计算方法,而且每一次计算高度都相当于创建了一个Cell,多耗性能,现在估算高度,直接相乘,然后真实高度直接从数组里面取。后来想了一下,还是觉得把创建Cell会布局得到的Cell高度存进模型里面,灵活性更高。直接以Model属性的形式存进模型里面。

SubView过多

  • 减少subviews的数量

  • UIView很重,创建和销毁都比CALayer要耗费资源,CALayer代替UIView,典型分割线

  • 不需要响应事件的视图,优先考虑CALayer

Cell 样式变化控制

  • addSubview、addSublayer、viewWithTag、removeFromSuperview大量的UI绘制会对内存和CPU产生负荷

  • 样式变化不大,使用hidden属性进行样式控制

  • Cell 样式变化很大,直接创建新的自定义Cell

优化图片加载方式

1、开辟很多内存将使用过的图片存储在缓存之中
UIImageView *image = [UIImageView imageView:@"1.png"];

2、使用完图片会立即丢弃释放资源。(工程中的大图&&使用次数很少 = 优先考虑)
UIImageView *image = [UIImageView imageWithContentOfFile:@"1.png"];

3、图片的解码:后台线程先把图片绘制到 CGBitmapContext中,然后从Bitmap直接创建图片


一张png格式的图片赋值给UIImageView或CGImageSource之后,依然是png格式,只有在CPU将要把所有视图转变成bitmap位图的时候,才会解码图片,这一步实在主线程完成的,一张图片的数据量极大,如果是一张高清大图,CPU的压力可想而知,因此通常所用的三方库都会先把图片异步从png格式变成位图二进制模式并保存在图片的位图上下文之中,这样GPU在图层合成的时候直接就实现了数据的合成。

图片集中加载卡顿

  • 拖拽滚动tableView -> NSRunLoopTrackingMode模式 - > 图片加载添加runLoop为NSDefaultRunLoopMode模式这个前提 -> 实现视图停止滚动才加载图片
// 只在NSDefaultRunLoopMode模式下显示图片
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];


所谓的图片异步绘制其实就是说,下载完图片之后异步将图片从png格式变成bitmap保存在图片位图上下文之中,然后获取imageView的上下文,把图片位图上下文中提取到的bitmap绘制在layer层上。即使异步渲染,图片掉帧依然严重,这就需要从CPU和GPU的配合来看了,GPU专门用来处理bitmap的textTure纹理的地方,通过纹理对输入的bitmap进行变换混合渲染等操作,无论是CPU把bitmap从内存提到显存进而绑定到GPU的纹理上,还是GPU正式处理bitmap的过程,CPU都是轻松的,GPU压力很大,所以一旦大量图片需要加载,GPU就会力不从心,必然就会出现在16ms内不能完成渲染任务的情况,于是掉帧不可避免的出现了,最好的解决办法就是按需加载,只是加载屏幕附近的Cell的图片。


为什么说减少视图的层次也能够提升性能,刚才说过GPU的textture纹理负责bitmap的混合,这个混合就相当于一个压缩的过程,把很多个图层的每一特性都要考虑到,然后决定最后的整体的样子,势必消耗性能巨大,假如说很多图层一开始就合成一张图片,就相当于原本10个图层,现在一个图层。alphe通道颜色的叠加通通不需要再考虑了。GPU 避免了多张 texture 合成和渲染的消耗,更少的 bitmap 也意味着更少的内存占用。

透明度卡顿

  • 图层半透明Or背景色clearColor -> 图层blend操作即alpha合成操作 -> GPU渲染 -> 极耗性能

  • 设置非透明视图的layer.opaque属性为YES,以避免无用的 Alpha 通道合成

  • 尽量使所有的view opaque,包括Cell自身

多个图片异步请求集中到达UI线程

  • 集中处理图片加载造成UI卡顿严重
  • performSelectorAfterDelay堆积图片加载操作到UI线程空闲的时候执行,副作用:滑动过程中不会加载任何图片
  • NSOperationQueue:队列中逐个执行

内容太多未缓存

主线程阻塞

同步变异步

  • 布局计算

  • 文本排版

  • 图片/文本/图形渲染

  • 图片解码

  • 绘制

性能优化

  • UIKit操作

  • Core Animation操作

按需加载Cell

本质需要明白的的及方法,第一获取当前屏幕的第一个Cell的indexPath,第二获取tableView的content最大y偏移对应的cell的indexPath,然后比较按需加载的前提,是否屏幕当前cell与目标Cell的行差大于设定值,如果大于,则执行按需加载的逻辑,很简单,本质上就是拒绝所有数据的reloadData,只刷新特定行的内容,首要就是找到特定行的indexPath,所以很容易,想获取当前屏幕上所有显示的Cell的indexPath集合,然后判断firstObject第一个indexpath.row是否大于三,然后把屏幕上方的三个indexPath也加入到数组之中。同理依然,一旦判断即使lastObject的indexpath加上3个屏幕下方的indexPath依然不会数组越界,就相当于直接就实现了tableView的数据按需加载。

复用Cell

  • 不仅仅节省内存,更可以节省创建Cell的时间

卡顿现象:

1、cell大量subview修改了layer的属性,例如mask、shadow、corRodius

2、cell视图中图片过大过多集中展示的时候,即使使用了三方库的图片异步加载

3、cell中大量的文本宽高结算boundingRectWithSize、CoreText排版绘制成bitmap显示

4、cell界面复杂,子视图巨多

优化性能的举措:

Cell创建:能用layer绝不用View、尽量避免layer属性的修改、少用clearView和alpha通道、drawRect异步绘制化繁为简、CoreText自定义文本控件

Cell使用:固定高预估高行高函数赋值和布局计算分离滚动条跳跃、cell复用内存开销、按需加载Cell本质reload指定的indexPath数组

Cell赋值:布局计算一步到位、图片解码加载、 避免UI绘制操作、图片过多的时候调整runloop

1、layer

2、Image(解码,加载)

3、cellHeight

4、绘制()

5、渲染(Alpha、)

6、解码(过大、过多)

7、布局计算(Cell高度、Cell创建)

界面卡顿的原因:

CPU:内存、属性、CoreText、Bitmap

GPU:纹理(位图)合成、OpenGL、

FPS:

VSync:交换数据、

你可能感兴趣的:(界面卡顿)