简述
App启动速度是用户对于App的第一印象,如果App启动的很慢非常有可能导致用户的流失。
在开发过程中和实际生活过程中,我们都会经常遇到这类的话题。
在工作中的时候,运营小伙伴会说我们的开发技术不如竞品,判断依据是什么呢?别人启动速度比我们的快好几倍。
在实际生活过程中,我们可能会依赖于各种各样的App,而这些App不乏缺少竞品,为什么我们会在芸芸众生中选择其中某一款呢?除了功能外启动速度也成了我们衡量的一种标准。
正文
App启动分类
- 冷启动
App启动前没有所以该App的进程在系统中,用户从点击App的icon那一刻开始到用户看到第一个页面,这一完整的启动过程。
- 热启动
App在冷启动之后,用户点击Home
或者其他方式将App切入到后台,这时候App不会被系统立马释放屌,还会继续存活若干时间,这个时候App的进程还在系统中,这种情况下,用户重新启动App的过程。
App启动过程
一般而言,App的启动主要有三个阶段:
-
main()
函数执行前 -
main()
函数执行后 - 首屏渲染完成
main()
函数执行前
- 加载可执行文件(App 的.o 文件的集合);
- 加载动态链接库,进行rebase指针调整和bind符号绑定;
- Objc运行时的初始处理,包括Objc相关类的注册、category注册、selector唯一性检查等;
- 初始化,包括了执行
+load()
方法、attribute(constructor)修饰的函数的调用、创建C++静态全局变量。
main()
函数执行后
main()
函数执行后的阶段,指的是从main()
函数执行开始,到AppDelegate
的didFinishLaunchingWithOptions
方法里的首屏渲染相关方式执行完成。
首屏的业务代码都是要在这个阶段,也就是首屏渲染前执行的,主要包括了:
- 首屏初始化所需要的一些配置文件的读写操作;
- 首屏列表数据的读取;
- 首屏渲染的一些计算等。
首屏渲染完成
- 非首屏其他业务服务模块的初始化;
- 其他业务基础功能初始化;
- 监听的注册;
- 配置文件的读取等。
App启动速度优化方案
针对main()
函数执行前的优化
- 苹果公司建议使用更少的动态库,并建议在使用动态库的数量较多时,尽量将多个动态库进行合并。数量上,苹果公司最多支持6个非系统动态库合并为一个,减少动态库的加载;
- 减少加载启动后不会去使用的类或方法;
-
+load()
方法里的内容可以放到首屏渲染完成后在执行,或者使用+initialize()
方法替换掉。每一个+load()
方法里,进行运行时方法替换操作会带来4毫秒的消耗; - 控制
C++
全局变量的数量。
针对main()
函数执行后的优化
梳理出哪些是首屏渲染必要的初始化功能,哪些是App启动必要的初始化功能,哪些是只需要在对应功能开始使用时才需要初始化的,在梳理完之后将一些初始化功能放到对应的合适阶段中进行。
针对首屏渲染完成后的优化
这一部分主要是针对一些会卡住主线程的方法。
功能级别的优化
优化思路:main()
函数开始执行后到首屏渲染完成前,只处理首屏相关的业务,其它非首屏业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后去做
方法级别的优化
优化思路:检查首屏渲染完成前,主线程上还有哪些耗时方法,将没必要的耗时方法滞后或异步执行。
App启动速度的优化监控方案
定时抓取主线程上的方法调用堆栈,计算一段时间里的各种方法的耗时
利用Xcode
工具自带的Time Profiler
优点:开发成本低,IDE
自带
缺点:精度低,定时间隔设置时间长的话,会漏掉一些方法,导致检查出来的耗时不精确,定时时间间隔设置的时间短,抓取堆栈方法本身调用也会影响整体耗时,导致结果不精确。
对objc_msgSend
方法进行hook
来掌握所有方法的执行耗时
即在原方法开始执行时,换成其他指定的方法,或者在原有方法执行前执行指定的方法,来达到空我和改变指定方法的目的。
优点:非常精确
缺点:只能针对Objc
的方法,如果是针对C
方法和block
可以使用libffi
的ffi_call
来完成,但是便携和维护相关工具的门槛比较高。
相关工具:
Facebook 开源了一个库fishhook,可以在 iOS 上运行的 Mach-O 二进制文件中动态地重新绑定符号。
网友评论