成像过程:
- 在iOS中是双缓冲机制,有前帧缓存、后帧缓存
成像原理:
-
发出垂直同步信号,告诉屏幕即将显示一帧数据;
-
发出水平同步信号,显示一行一行的数据,直到填充到整个屏幕为止。
产生卡顿的原因
-
如图:垂直同步信号的发射频率是固定的,信号发出,代表着即将显示数据;
-
如果期间,CPU或GPU有一步耗时较长(
第3帧的渲染
),垂直信号已发出,但是GPU还没有渲染完,那么就是显示渲染好的
第2帧
数据,连续显示相同的帧,就造成了画面卡顿。
-
第3帧
会在第4次同步信号过来时再显示。
所以解决卡顿的主要思路:
- 尽可能减少CPU、GPU的资源消耗。(保证刷帧率60FPS.)
-
-
CPU优化:
-
尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView;
-
不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改;
-
尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性;
-
Autolayout会比直接设置frame消耗更多的CPU资源;
-
图片的size最好刚好跟UIImageView的size保持一致;
-
控制一下线程的最大并发数量;
-
尽量把耗时的操作放到子线程:
- 图片处理(解码、绘制);
- 文本处理(尺寸计算、绘制):
// 文字计算 [@"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