App 启动及优化

App 启动流程及优化梳理

App 启动类型

App 根据是否存在于内存中以及进程是否存活可以大致分为三种启动:

  • 冷启动:App 没有被加载到内存中,也没有对应的进程存活
  • 热启动:App 被加载到内存中,但是没有对应的进程存活
  • 恢复响应:App 被加载到内存中,且有对应的进程存在。仅仅是从挂起状态恢复

App 启动流程

App 启动时会调用两个标志性的函数:

  1. exec 函数:启动时调用,该函数主要是主要是链接、加载依赖的动态库以及初始化语言的 Runtime
  2. main 函数:系统载入和初始化完成之后调用,开启 App 的初始化

整个启动过程可以大致分为6个阶段

App Launch phases

System Interface

动态链接

系统调用阶段第一步主要是 DYLD(动态库加载器) 加载所依赖的共享库动态库到 App 进程

官方最新的 DYLD3 相对于 DYLD 2 在载入阶段做了大量的优化

dyld3 vs dyld2

mach-o 是 App 自身的可执行文件(.o)集合

在 DYLD3 中,将三个部分放到了进程之外执行并将结果缓存

  1. 查找和解析 mach-o 的头文件
  2. 分析依赖的动态库
  3. 检查对应的符号表

dyld3 cache

DYLD 3 的缓存操作是在 App 安装和更新的时候进行的

在这个阶段优化的思路主要有:

  • 减少加载的时间:
    • 避免链接不使用的动态库(此建议针对官方提供的动态库,第三方动态库不使用则应该删除)
    • 将一些不常用的非必须动态库延迟动态加载
  • 利用系统提供的缓存机制:避免在启动时动态加载动态库,因为动态加载的库不会被缓存,会拖慢 App 的启动时间
  • 加快加载的时间:减少外部动态库的数量,可以将一些分散的功能单一的库合并成一个
  • 加快 DYLD3 Bind&Rebase 时间:Bind&Rebase 会查找并调整指针,所以减少指针数量可以加快速度
    • 删除不用的类和方法
    • 减少分类的数量
    • 对于 Swift 可以尽量使用 struct (可以减少符号的数量)

由于ASLR(address space layout randomization)技术会导致可执行文件和动态链接库在虚拟内存中的加载地址每次启动都不相同,所以需要修复镜像中的资源指针,使之指向正确的地址。rebase 修复的是指向当前镜像内部的资源指针; 而 bind 指向的是镜像外部的资源指针。 rebase 步骤先进行,它会把镜像读入内存,并以 page 为单位进行加密验证,保证不会被篡改,所以这一步的瓶颈在 IO。随之进行的是 bind 步骤,由于要查询符号表来指向跨镜像的资源,同时需要解密在rebase阶段已被读入和加密验证的镜像,所以这一步的瓶颈在于CPU计算

libSystem Init

系统调用阶段第二步主要是初始化系统中的动态库;这一步没有什么优化的空间

常用的系统动态库如 libdispatch(GCD) 和 libsystem_blocks (Block)

Runtime Init

第二个阶段是 Runtime 的初始化,这时会初始化 Objc 和 Swift 的 Runtime;之后会调用所有类的静态 + (void)load 方法

这个阶段的优化主要是在调用类的静态 Load 方法时,很多库喜欢在 Load 方法中调用初始化逻辑保证第一时间做一些处理。但是这会减慢 App 的启动时间,尤其是大多数库并不会第一时间被使用的情况下更加浪费时间。因此优化的思路是:

  • 尽量避免在 +[Class load] 中插入逻辑;Swift 中已经废弃这个方法
  • 可以使用 +[Class initialize] 方法来进来静态变量的懒初始化;这个方法会在类第一次初始化之前被调用
  • 最好提供库的初始化方法来保证在使用该库时再进行初始化

UIKit Init

第三个阶段是 UIKit 初始化阶段;在这个阶段会实例化 UIApplicationUIApplicationDelegate;同时开启系统的事件交互和交互

这个阶段的优化思路是尽量快的实例化 UIApplicationUIApplicationDelegate

  • 减少 UIApplication 子类的实例化时间
  • 减少 UIApplicationDelegate 实例化的时间

Application Init

在这一阶段会触发 App 的启动回调

  • App 生命周期回调
    • application:willFinishLaunchingWithOptions:
    • application:didFinishLaunchingWithOptions
  • UI 生命周期回调 applicationDidBecomeActive:
  • iOS 13 以上使用 SceneKit 会触发每个 scene 的 UISceneDelegate 的 UI 生命周期回调
    • scene:willConnectToSession:options:
    • sceneWillEnterForeground:
    • sceneDidBecomeActive:

当 App 只是从挂起状态恢复时只会触发 UI 生命周期回调

优化方法是尽量较少回调中的逻辑,保证回调执行的速度

Initial Frame Render

这一阶段开始进行第一帧的界面绘制,会经历创建View(loadView & viewDidload)、Layout(layoutSubviews)、然后最后到绘制到屏幕上的过程

这一阶段的优化建议加快渲染的速度,可以在第一帧只展示一个尽可能简单的界面。只展示重要的数据,让用户尽量快的看到界面并且可以交互

Extended

到这一阶段已经对 App 有足够的控制,尽量保证不要阻塞用户操作和响应即可

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2017-2021 HonQi

请我喝杯咖啡吧~