二十七、启动优化分析

2020-11-16  本文已影响0人  KB_MORE

当用户按下home键的时候,iOS的App并不会马上被kill掉,还会继续存活若干时间。理想情况下,用户点击App的图标再次回来的时候,App几乎不需要做什么,就可以还原到退出前的状态,继续为用户服务。这种持续存活的情况下启动App,我们称为热启动,相对而言冷启动就是App被kill掉以后一切从头开始启动的过程。我们这里只讨论App冷启动的情况。对于冷启动来说,启动时间是指从用户点击 APP 那一刻开始到用户看到第一个界面这中间的时间。

冷启动
热启动
冷启动与热启动的区别和场景

【区别】内存中是否有加载的数据

【场景】

  1. 启动性能检测和分析

2. 启动性能检测和分析

测试APP启动,分为两个阶段:

系统处理,我们从dyld应用加载的流程来优化。(借助系统工具分析耗时)

通过检测业务流程优化main函数打个时间点第一个页面渲染完成打个时间点。测算耗时)

3. APP启动时间优化原则

对于启动时间优化其实就是遵循一个原则:尽早让用户看到首页内容
根据这一原则将一些非必须的操作尽量往后移,通常是移到首页显示后执行,同时对于无法往后移的操作,尽可能不占用主线程,主线程尽量只做 UI 操作,将其他操作移到子线程。多利用CPU性能,在启动时发挥用多线程能力

4. APP启动过程

iOS应用的启动可分为pre-main阶段main()阶段,其中系统做的事情依次是:

pre-main阶段

main()阶段

5. 启动耗时的测量

在进行优化之前,我们首先应该能测量各阶段的耗时。

pre-main阶段测量

在不越狱的情况下,以往很难精确的测量在main()函数之前的启动耗时,因而我们也往往容易忽略掉这部分数据。小型App确实不需要太过关注这部分。但如果是大型App(自定义的动态库超过50个、或编译结果二进制文件超过30MB),而我们的App的自定义动态库较多,且二进制文件接近60M,这部分耗时将会变得突出,需要我们优化pre-main阶段的时间。所幸,苹果已经在Xcode中加入这部分的支持。
具体设置方法如下:

在Xcode 中 Edit scheme -> Run ->Auguments 将环境变量DYLD_PRINT_STATISTICS设为1

image

还需要勾选下面这个选项:

image

设置好后把程序跑起来,控制台会有如下输出,pre-main阶段各过程的耗时从图上可以看出加载时间最长的阶段,咱们可以有针对性的检查并优化此过程。

image

5.1. 时间耗时解读

main()函数之前总共使用了2000ms其中,加载动态库用了563.59.ms,指针重定位使用了910.80ms,ObjC类初始化使用了321.32ms,各种初始化使用了235.84ms。我们从上面的表中可以清晰的看出在哪个阶段耗时比较多,从而为下一步的优化提供指导。

main()阶段测量
对于main()阶段,主要是测量main()函数开始执行didFinishLaunchingWithOptions执行结束的耗时,就需要自己插入代码到工程中了。先在main()函数里记录当前时间,再在AppDelegate.m文件中的didFinishLaunchingWithOptions函数的最后获取一下当前时间,这两个的时间的差值即是main()阶段运行耗时。

6. 优化思路及明确优化方向

在通过以上方法测量出各阶段的占用时间,从数据上分析哪个阶段占用的时间多,从而指导我们明确优化的方向。

是什么影响了我们的APP的启动时间?

切忌挖空心思的研究优化main()函数调用之前的占用时间,反而忽略了-applicationDidFinishLaunching:函数之后那一堆堆臃肿的网络请求以及业务流程。所以我们先来看看-applicationDidFinishLaunching:函数之后,我们的APP都做了哪些事情:首先会初始化window,加载tabbar,加载首页controller以及数据,可能我们还有一个loading广告页,还有各种各样的业务需求,网络请求。所以这些都是需要去排查的地方,可以尝试通过添加打印时间戳的方式,来测量每个阶段的耗时情况。我们根据排查结果来明确造成启动缓慢的原因。

7. pre-main阶段加载过程

要对pre-main阶段的耗时做优化,需要再学习下dyld加载的过程,dyld的加载主要分为4步:

7.1 加载dylibs

这一阶段dyld会分析应用依赖的dylib(大部分是iOS系统的),找到其mach-o文件,打开和读取这些文件并验证其有效性,接着会找到代码签名注册到内核,最后对dylib的每一个segment调用mmap()

7.2 Rebase/Bind

这一阶段系统主要注册 Objc 类。所以,指针数量越少越好。

7.4 Objc setup

OC的runtime需要维护一张类名与类的方法列表的全局表。
dyld做了如下操作:

在这一步倒没什么优化可做的,Rebase/Bind阶段优化好了,这一步的耗时也会减少。

7.5 Initializers

这一阶段,dyld 开始运行程序的初始化函数,调用每个 Objc 类分类+load 方法,调用 C/C++ 中的构造器函数。initializer阶段执行完后,dyld 开始调用 main() 函数。

7.3 pre-main阶段耗时优化

通过以上的pre-main阶段过程的分析我们得到如下结论:

8main()阶段的耗时优化

此阶段的优化才是我们app优化的核心与重点,大部分的启动时间消耗出现在此阶段:这一阶段的优化主要是减少didFinishLaunchingWithOptions方法里的工作,在didFinishLaunchingWithOptions方法里,我们会创建应用的window,指定其rootViewController,调用window的makeKeyAndVisible方法让其可见。由于业务需要,我们会初始化各个三方库,推送、定位、im、埋点上报等基础服务的初始化,检查是否需要显示引导页、是否需要登录、是否有新版本等,由于历史原因,这里的代码容易变得比较庞大,启动耗时难以控制。所以,满足业务需要的前提下,didFinishLaunchingWithOptions在主线程里做的事情越少越好,可以把一些事情放在子线程去处理。
优化方向及方案

上一篇下一篇

猜你喜欢

热点阅读