小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
什么是卡顿
卡顿也就是所谓的掉帧、花屏。cpu对渲染的东西计算,GPU负责渲染,渲染的东西到了帧缓存区(framebuffer
),然后通过VideoController交给Monitor
来显示。
当CPU计算卡住的时候,VideoControlle
就一直空等,会造成浪费的情况。此时系统推出了前帧和后帧也就是双缓存机制。但是此时又会发生这么一种情况,在换帧的时间间隔里,如果有一个帧没有被渲染出来,就会丢弃,也就是掉帧。这个来回在双缓存之间切换的时间也就是垂直同步信号VSync
,在这个时间间隔之内能够计算完就能够正常显示,计算不完就丢掉。
**CADisplayLink**
卡顿检测
YYKit
的YYFPSLable
利用CADisplayLink
来检测,因为这个是绑定在Vsync上的计时器。一般都是60
_link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- (void)tick:(CADisplayLink *)link {
if (_lastTime == 0) {
_lastTime = link.timestamp;
return;
}
_count++;
NSTimeInterval delta = link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = link.timestamp;
float fps = _count / delta;
_count = 0;
// ...
}
runloop
卡顿检测
- 首先在主线程的runloop上加上一个
Observer
和对应的callBack回调,该callBack
回调**发送信号**dispatch_semaphore_signal+1
- (void)registerObserver{
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
//NSIntegerMax : 优先级最小
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
NSIntegerMax,
&CallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
LGBlockMonitor *monitor = (__bridge LGBlockMonitor *)info;
monitor->activity = activity;
// 发送信号
dispatch_semaphore_t semaphore = monitor->_semaphore;
dispatch_semaphore_signal(semaphore);
}
- 在子线程开启一个
do-while
常驻线程, 在该子线程中创建一个信号量,设置dispatch_semaphore_wait
的超时时间是1s。如果主线程不卡顿的话,子线程wait
之后主线程马上signal
,此时返回值应该是0,如果不为0的话此时就表明有卡顿存在。
- (void)startMonitor{
// 创建信号
_semaphore = dispatch_semaphore_create(0);
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (YES)
{
// 超时时间是 1 秒,没有等到信号量,st 就不等于 0, RunLoop 所有的任务
long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
if (st != 0)
{
if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)
{
if (++self->_timeoutCount < 2){
NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);
continue;
}
// 一秒左右的衡量尺度 很大可能性连续来 避免大规模打印!
NSLog(@"检测到超过两次连续卡顿");
}
}
self->_timeoutCount = 0;
}
});
}
微信卡顿检测工具
Matrix
里面的方法addMonitorThread
卡顿检测跟上面的runloop
大致相同
怎么解决卡顿
- 预排版:在网络数据请求回来之后,根据数据提前计算排版
- 预解码 & 预渲染(加载图片的时候)
UIImageView
的结构组成它其实是一个模型, 图片的加载流程
我们可以参考下SDWebImage
这个第三方在task成功之后做了什么操作,找到这个结果回调的函数
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
SDWebImage
在获取到结果之后开启了一个子线程异步解码编码队列中的图像SDImageLoaderDecodeImageData
,那么为什么这么做呢。当我们调用这个[[UIImage] imageWithData:]
方法时,苹果默认是在主线程来解码这些数据。而在实际应用中大部分卡顿的时间都在加载图片上,所以SDWebImage
在子线程重写了解码的方法。
图形编解码插件音视频FFmpeg
- 按需加载:如果Cell不断的滚动此时可以只处理可视范围内的数据
- 异步渲染:将要绘制的操作放到异步绘制,主线程只负责刷新展示
UIView & UILayer
Commint Transaction
做了什么
layout
: 构建视图frame , 遍历操作[UIViewlayerSubview][CALayer layoutSubLayers]
display
: 绘制 display--drawRect()
,displayLyaer:(位图的绘制)Prepare
: 额外的CoreAnimation工作,比如解码Commi
: 打包图层并将它们发送到RenderServer