前言

应用启动是用户使用体验的第一步,应用启动时间越短,等待的时间就越短,用户使用体验就越好。所以,启动时间是体现应用性能优劣的一个重要指标。

应用启动方式

字节团队根据场景的不同,将应用启动可以分为三种:冷启动,热启动和回前台。

冷启动:系统里没有任何进程的缓存信息,典型的场景是重启手机后直接启动 App。热启动:如果把 App 进程杀掉,立刻重新启动 App,这次启动就是热启动,因为进程缓存还在。回前台:进入后台的应用依然存活着,处于suspended状态。

当然,也有另一种分类方式,将应用启动分为两种:冷启动和热启动。

冷启动:App 启动时,应用进程不在系统中(初次打开或程序被杀死),需要系统分配新的进程来启动应用。热启动:App 退回后台后,对应的进程还在系统中,启动则将应用返回前台展示。

我们关注的应用启动时间优化,一般都是指冷启动的时间优化,所以这里不探讨具体的启动分类方式孰对孰错。

应用启动流程

在抖音品质建设 - iOS启动优化《原理篇》这篇文章中,字节团队详细说明了应用启动过程中各个步骤的具体工作。对于我们开发人员,可以将应用启动归纳为这个流程:

埋点监控

根据上面的应用启动流程,我们需要关注这么几个时间点:

进程的创建时间,通过 sysctl 系统调用拿到进程创建的时间戳。第一个+load方法的触发时间,+load调用顺序和链接顺序有关,链接顺序默认按照 CocoaPod 的 Pod 命名升序排列,所以取一个命名为 AAA 开头既可以让某个 +load 第一个被执行。didFinishLaunching方法的触发时间首帧展示时间,抖音品质建设 - iOS启动优化《实战篇》文章中帮我们总结出了一个无侵入获取首屏渲染方案是: iOS13(含)以上的系统采用 runloop 中注册一个 kCFRunLoopBeforeTimers 的回调获取到的 App 首屏渲染完成的时机更准确。 iOS13 以下的系统采用 CFRunLoopPerformBlock 方法注入 block 获取到的 App 首屏渲染完成的时机更准确。

启动时间优化

启动时间优化的整体思路可以总结为:

移除不必要的启动项将可以放在启动后处理的任务延迟执行利用多核多线程并发执行任务优化耗时操作,使用更优的方案执行相同的任务

1 Pre-main阶段

Pre-main之前的具体操作流程:

加载 dyld创建启动闭包(更新App或者是重启手机时需要)加载动态库Bind & Rebase & Runtime 初始化+load 和静态初始化

1.1 加载动态库方面

减少动态库数量,也不要链接那些用不到的库(包括系统库),可以加减少启动闭包创建和加载动态库阶段的耗时,官方建议动态库数量小于 6 个。动态库转静态库,因为还能额外减少包大小。合并动态库,这一点在实践操作过程中成本比较高。

1.2 Bind & Rebase & Runtime方面

删除不会调用的代码逻辑,包括没用到类、方法、属性等等。简化代码逻辑,用更精简的代码完成相同的任务。

那么如何查找应用内不会调用的代码逻辑呢?可以尝试这种方式:

借助工具SwiftLint、infer、oclint等,具体可以参考IOS静态代码扫描–分析与总结和如何通过静态分析提高iOS代码质量基于Mach-O,通过 __objc_classlist 找到所有的 sel 和 class,再通过 _objc_selrefs 和_objc_classrefs 找到引用到的 sel 和 class,然后通过计算两个集合的差集就能得到未使用的 sel 和 class。不过,因为iOS的运行时机制,删除代码前需要再次确认。埋点统计:ViewConteroller的渗透筛查,hook 对应的声明周期方法即可统计;Class的渗透筛查,遍历运行时的所有类,通过 Objective C Runtime 的标志位判断类是否被访问;行级渗透率,需要用编译期插桩(Facebook 给 LLVM 提的order_file的 feature 就是实现了类似的插桩),对包大小和执行速度均有损。

1.3 +load 和静态初始化方面

+load迁移,+load本身需要耗时处理,同时会引起大量 Page In,而且+load内出现crash无法捕获。静态初始化迁移,静态初始化也会引起大量 Page In,一般来自 C++代码,比如网络或者特效的库,可以通过预处理来确认。典型的迁移思路有 1、std:string 转换成 const char * 2、静态变量移动到方法内部,因为方法内部的静态变量会在方法第一次调用的时候初始化

2 After-main阶段

在main函数之后,首帧展示之前,优化启动时间的主要任务就是减少didFinishLaunchingWithOptions方法里的工作,在主线程里做的事情越少越好。

梳理三方SDK,移除可以移除的SDK,延迟可以延迟加载的SDK。业务逻辑优化: 1 通过多线程并发执行任务; 2 梳理出非紧急的任务延迟执行;耗时操作优化: 1 一些方法的处理耗时较长,那有没有其它更快的方案替代呢; 2 一些高频次方法,比如[[NSBundle mainBundle] infoDictionary],虽然单次耗时不长,但是其累计耗时却不低;图片资源,用Asset存放图片,Asset 会在编译期做优化,让加载的时候更快。第一次加载Asset图片时,因为要建立索引耗时会长一些,可以启动需要用到的图片单独存放在一个Asset来减少这部分耗时。首帧渲染: LottieView: lottie 是 airbnb 用来做 AE 动画的库,但是加载动画的 json 和读图是比较慢的,可以先显示一帧静态图,启动结束后再开始动画,或者子线程预先把图和 json 设置到 lottie cache 里。 View懒加载: 不需要在首帧展示的view,不要先创建设置成hidden,可以通过懒加载的方式创建view。 AutoLayout: AutoLayout耗时也较高,可以评估 ROI 是否可以改成 frame。 Loading动画: 首屏loading动画最好不要用gif,可以加载lottie动画,具体加载动画策略可以参考gif加载框架选型分析。

参考文章

抖音品质建设 - iOS启动优化《原理篇》 抖音品质建设 - iOS启动优化《实战篇》 阿里数据iOS端启动速度优化的一些经验 gif加载框架选型分析

相关链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。