目录
一、性能优化从哪几方面入手?
二、卡顿优化
1.屏幕成像原理
2.卡顿原因
3.卡顿解决办法
3.1卡顿优化 — CPU
3.2卡顿优化 — GPU
4.卡顿检测
总结
前言
随着移动互联网的快速发展,各个领域都出现了巨头,各大App不再是以实现功能抢占市场,而是把App的用户体验做到最好,性能优化就是决定一款产品体验好坏的关键因素,也一定程度决定了用户的忠诚度和市场占有率,本文介绍几种常见的性能优化方式,让我们的App有更好的体验。
首先介绍两个处理器:CPU和GPU
CPU:中央处理器(CPU,central processing unit)作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。
GPU:图形处理器(英语:Graphics Processing Unit,缩写:GPU),又称显示核心、视觉处理器、显示芯片,是一种专门在个人电脑、工作站、游戏机和一些移动设备(如平板电脑、智能手机等)上做图像和图形相关运算工作的微处理器。
CPU就像机器的大脑,主要负责计算工作,比如对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制等,图像的渲染就交给它的小弟GPU去做。
从屏幕成像过程可以看出,CPU完成显示图像的大小位置内容等计算后,交给GPU去绘制,GPU绘制完成一帧图像后放到帧缓存区,iOS中是双缓冲机制,有前帧缓存和后帧缓存,提高了渲染效率。
我们看到的动态屏幕其实也是一帧一帧的静态画面组成的,显示器会用硬件时钟产生一系列的定时信号,电子枪准备扫描新的一行时,显示器会发出一个水平同步信号(Horizonal Synchronization),简称 HSync,当前屏幕完全扫描完成,再次滑动时,需就要绘制新的一帧画面,显示器会发出一个垂直同步信号(Vertical Synchronization),简称 VSync,iPhone是以固定频率刷新进行刷新,默认刷新率是60hz,垂直同步信号大概16.7ms发送一次,
每隔16.7ms,视频控制器来帧缓存区拿数据去屏幕显示,
卡顿产生的原因就是CPU和GPU没有及时处理好数据,针对卡顿的优化就有思路了:尽可能减少 CPU 和 GPU 资源的消耗
Autolayout
会比直接设置 frame 消耗更多的 CPU 资源,相对布局最终到CPU那里也是计算出视图的绝对大小和位置的,如果不是特备复杂的位置关系,尽量用绝对布局,减少CPU的计算离屏渲染
在OpenGL中,GPU有2种渲染方式
离屏渲染消耗性能的原因
哪些操作会触发离屏渲染?
卡顿是因为主线程执行了耗时的操作,通过添加 Observer 到主线程 RunLoop 中,监听 RunLoop 状态的切换的耗时,达到监控卡顿的目的。
一张经典的RunLoop运行逻辑和对应的状态方法
// 1.进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled)
// 2.RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
// 3.RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
// 4.RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle)
// 5.执行被加入的block等Source1事件
__CFRunLoopDoBlocks(runloop, currentMode);
// 6.RunLoop 的线程即将进入休眠(sleep)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
// 7.调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort)
// 进入休眠
// 8.RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting
// 9.1.如果一个 Timer 到时间了,触发这个Timer的回调
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
// 9.2.如果有dispatch到main_queue的block,执行bloc
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
// 9.3.如果一个 Source1 (基于port) 发出事件了,处理这个事件
__CFRunLoopDoSource1(runloop, currentMode, source1, msg);
// 10.RunLoop 即将退出
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
由于source0处理的是app内部事件,包括UI事件,所以可知处理事件主要是在kCFRunLoopAfterWaiting 和kCFRunLoopBeforeSources 之间。我们可以创建一个子线程去监听主线程状态变化,通过dispatch_semaphore在主线程进入状态时发送信号量,子线程设置超时时间循环等待信号量,若超过时间后还未接收到主线程发出的信号量则可判断为卡顿,保存响应的调用栈信息去进行分析。可参考LXDAppMonitor
卡顿优化工作是一个复杂而艰巨的过程,尤其到后期更是如此,越是庞大的项目越需要对流畅度做优化,它涉及到代码的重构、逻辑的重写,甚至是底层架构,牵一发而动全身,所以开发人员必须在熟悉现有业务逻辑的前提下做优化,在不发生crash的情况下尽可能保持App的流畅性如行云流水一般。