APP项目优化--启动速度优化篇
我们所开发的项目,随着线上功能逐渐稳定,导致项目启动速度越来越慢,而这又是用户对我们的项目给第一印象,所以启动速度变得尤为重要,如果启动速度缓慢,会造成比较严重的用户流失,所以,对启动速度的优化,将会成为我们后期开发工作中不可或缺的一部分。
基础概念
冷启动:是指启动并没有进程在系统里,需要系统新创建一个进程供APP使用的启动情况
热启动:和冷启动对应,是APP的进程在系统里,用户重新启动进入APP的过程,如果把APP进程杀掉,然后立即重启,也属于热启动因为进程的缓存依然存在
其实,从用户可感知的维度去看呢,就是用户在手机桌面,点击APP图标,到APP启动图完全消失,首页第一帧渲染完成的过程。所以,本文我们就只展开说APP冷启动的流程和优化。
启动流程
总得来说APP启动主要包括三个阶段:
1、main()函数执行之前
2、main()函数执行之后
3、首屏渲染完成后
关于 APP 启动时间,主要由 pre-main 和 main 之后的时间组成,即总的启动时间 = main 之前加载的时间 + main 之后加载的时间。启动时间越接近 400ms 越好,并且最好控制在 20s 以内,不然系统会以为 APP 进入一个死循环,应用进程将会被系统强制杀除。
下面我们继续分别说一说这三个阶段系统都做了什么事情。
main()函数执行前
在main()函数执行前,系统主要会做下面几件事:
- 加载可执行文件(APP的.o文件的集合),就是Mach-o文件,这个在后面会详细介绍
- 加载动态链接库,进行rebase指针调整和bind符号绑定
- 运行时的初始化工作,包括类的注册,category注册,selector唯一性的检查等
- 执行+load()方法,attribute((constructor)) 修饰的函数的调用,创建 C++ 静态全局变量
运行的时候就可以打印出详细的pre-main的时间
可以看到main()方法之前的时间由dylib loading ,rebse/binding,ObjC setup和initializer四个耗时的部分组成。
所以,相对应这个阶段,我们可做的优化工作如下:
- 减少动态库加载。每个库本身都有依赖,系统会递归的加载所有依赖的动态库,所以苹果公司建议,尽量使用更少的动态库
- 减少无用的类、分类和方法
- +load()方法中的内容如非必要,可以放到首屏渲染完成之后再执行,或者放在+initialize() 方法中执行。因为一个+load()方法会带来4毫秒的消耗
- 控制C++ 静态全局变量的数量
main()函数执行后
main()函数执行后,是指从main()函数开始,到appDelegate 的 didFinishLaunchingWithOptions 方法里首屏渲染相关方法执行完成的过程。这个过程主要包括了:
- 日志、统计
- 配置APP运行需要的环境
- 第三方SDK初始化
代码中,各种各样的初始化工作,全部都放在这个阶段去执行了,导致渲染缓慢。这里我们能做的优化工作是,从功能上梳理出来,到底哪些才是首屏渲染必要的初始化功能,哪些是APP启动必要的初始化功能,其他只需要在对应模块功能使用前才需要初始化的工作,分别放在他们对应的合适的位置。
除此之外,使用 instrument 可以帮助我们进行分析didFinishLaunchingWithOptions中的耗时操作。
首屏渲染完成后
到这个阶段,用户已经看到我们APP的首页,这个阶段,所做的事情是其他业务模块的一些初始化工作,也就是appDelegate 的 didFinishLaunchingWithOptions 方法作用域结束的位置。
所以这个阶段的优化优先级比较低,但是还是要注意,那些会阻塞主线程的操作,以防影响用户后面的操作。
优化工作
动态库加载方面
因为主要的动态库都是系统的动态库,而系统本身对其都有相应的优化处理,所以我们能做的只有去掉无用的系统动态库,或者一步到位直接删除掉Link Binary With Libraries中的所有系统动态库,改为自动link系统动态库
方法级别的优化
完成了功能级别的优化后,APP的启动速度应该已经有了一定程度的缩短,下面我们再继续从方法级别去做优化。
删除无用代码
当我们的项目日渐壮大,业务线交错的时候,也许已经有了很多无用的冗余代码在里面,我们可以使用AppCode分析检测项目代码。
抽象重复代码
1、在iOS代码中可能会为同一个类写很多分类方法,由于参与开发同学较多,可能会导致方法重复,但是实际上运行起来只能有一个分类的方法被调用,这取决于哪个分类后被加载,然而编译的二进制代码中,两个方法应该是都存在的,这不仅会增加app体积,也会增加启动时间,所以应该杜绝这样的重复问题;
2、有很多地方可能是名字不同,但是函数的功能相同,这个不容易被发现,需要大家在写代码的过程中注意;
3、又或者两个函数名字比较接近,里面有很多相似的代码,这种情况下可以进行相同的代码的提取。