小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
什么是卡顿
卡顿也就是所谓的掉帧、花屏。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