APP启动过程分析
本文主要通过我们对于APP启动过程的分析,然后去剖析如何去进行启动时间的优化,以达到APP性能的提升。
一:启动方式
1.1 冷启动
App 启动时,应用进程不在系统中(初次打开或程序被杀死),需要系统分配新的进程来启动应用。
1.2热启动
App 退回后台后,对应的进程还在系统中,启动则将应用返回前台展示。
本次我们只针对于冷自动做主要分析。
二:启动流程
Apple 官方的《WWDC Optimizing App Startup Time》 将 iOS 应用的启动可分为 pre-main 阶段和 main 两个阶段,最佳的启动速度是400ms以内,最慢不得大于20s,否则会被系统进程杀死(最低配置设备)。
因此:
启动流程 = pre-main + main函数代理(didFinishLaunchingWithOptions)+ 首屏渲染(viewDidAppear)
2.1 pre-main过程
1.首先启动会加载解析APP的info.plist文件,因为该文件包含了APP加载所需要的众多配置项,例如APP启动图,是否全屏等等。
2.创建沙盒(iOS8之后,每次启动都会生成一个新的沙盒路径).
3.根据info.plist配置项检查APP所申请的权限状态。
4.加载Mach-O文件,读取dyld路径并运行动态连接器。
<1>首先dyld会寻找合适的cup运行环境。
<2>加载程序所依赖的库以及.h/.m文件编译成的.o可执行文件,并对这些库进行连接。
<3>加载所有方法,runtime就是在这个时候被初始化并完成OC的内存布局。
<4>加载C函数。
<5>加载类扩展,category,此时runtime会对所有类结构进行初始化。
<6>加载C++静态函数,加载+load方法。
<7>最后main函数被调用。
Mach-O文件是 OS X 与 iOS 系统上的可执行文件格式,类似于windows的 PE 文件。像我们编译产生的.o文件、程序可执行文件和各种库等都是Mach-O文件。
2.2main函数代理
代理函数中didFinishLaunchingWithOptions执行
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:YES] ;
[self setupSDKConfig];
[RTPermissionManager registerRemoteNotification:self];
[self switchRootViewController];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
2.3首页渲染
home页面渲染。
三:影响启动时间的因素
对于APP的启动,可以说每一个时间段都有时间的性能问题,但是有些步骤损耗的时间非常短暂,如果我们花费大力气去处理,但是收益却很少,显然是不合适的,因此我们就从几个大的可以影响性能的方向入手。
3.1 main 函数之前
从上述的启动过程中可以看到,影响的因素很多,大体上可以分为:
<1>动态库的数量。
<2>OC类的数量,category的数量。
<3>C的constructor函数越多,启动越慢。
<4>C++静态对象越多,启动越慢。
<5>OC类的+load方法。
3.2 main 函数之后
main函数之后,我们就是会执行Appdelegate中的代理以及首页页面的渲染,因此这一部分的影响因素,大体上可以分为:
<1>执行main函数的耗时。
<2>执行Appdelegate中的applicationWillFinishLaunching的耗时。
<3>APP首页的渲染耗时。
四:APP执行时间
4.1 main函数之前
我们可以通过xcode的方法,在Project→Scheme→Edit Scheme中通过设置参数,然后可以在控制台打印对应的时间日志。
xcode运行,我们可以得到准确的数据。
iPhoneX
Total pre-main time: 770.45 milliseconds (100.0%)
dylib loading time: 150.62 milliseconds (19.5%)
rebase/binding time: 6.02 milliseconds (0.7%)
ObjC setup time: 9.22 milliseconds (1.1%)
initializer time: 604.57 milliseconds (78.4%)
slowest intializers :
libMainThreadChecker.dylib : 45.22 milliseconds (5.8%)
libglInterpose.dylib : 428.37 milliseconds (55.6%)
PuddingPlus : 189.86 milliseconds (24.6%)
从上面可以看出,整个过程耗时770ms,其中我们分析,动态库的加载,initializer和libglInterpose比较耗时,好了,我们已经知道时间在哪里浪费了,那么接下来就可以具体去一一对应解决了。
<1>加载动态库部分
进行代码瘦身,code清理,同时清理无用的资源和代码,毕竟APP体积增大也会拖慢启动速度,排查项目中引用的动态库,及时删除无用的资源。
<2>initializer 部分
排查项目中+load方法的使用,规避一些在APP启动时的非必要操作,可以延迟到冷启动之后的某一个时间点来做。
<3>libglInterpose
据我查找,这个是Xcode在debug时会加载的一个动态库,本次不做深究。
4.2 main函数之后
时间我们可以通过main函数入口以及Appdelegate打印具体的时间。
<1>main函数执行,如果里面没有主动加入任何耗时的任务,可以直接跳过。
<2>Appdelegate中的didFinishLaunchingWithOptions方法的优化
大部分APP在此方法中都会去实例化一些APP启动必须的类,或者注册一些监听,因此我们需要排查分析,是否每一个注册都是必须的,根绝我们的业务需要,适当的延后处理可不可以,比如APP的log日志部分,对于需要上传的日志部分,我们完全可以等到APP启动完成之后再做处理。
<3>首页渲染
*可以采取懒加载的方式,尽量延后执行我们所有类的实例化。
*采用facebook的模式,预先加载无数据页面,等待网络请求返回数据在刷新页面。
*采用缓存策略,预加载数据,减少用户等待的时间。
五:结语
对于快速迭代的App,随着业务复杂度的增加,冷启动时长会不可避免的增加。因此一次简单的优化是不行的,只能通过不断的努力,来不断的突破,同时还需要不要的监控日志平台分析数据,有效的业务管理,已经合理的设计,才能保证我们APP的良好体验。