App启动优化

一、冷启动和热启动
  • 定义:
    • 1.关于冷启动:业界对冷启动的定义没有问题,普遍认为是手机开机后第一次启动某个APP。
    • 2.关于热启动:
      对热启动有两种不同的看法:
      • 1.有些人认为是按下home键把APP挂到后台,之后点击APP的icon再拉回来到前台算是热启动;
      • 2.也有些人认为是手机开机后在短时间内第二次启动APP(杀掉进程重启)算是热启动(此时dyld会对部分APP的数据和库进行缓存,所以比第一次启动要快)。

(一般认为APP从后台拉起到前台的时间没啥研究的意义,所以在统计启动时间时,会倾向于后一种说法,不过具体怎么定义看个人,知道其中的区别就好)

如果启动过,内存页就已经加载进内存了,内存页如果要销毁只能是被覆盖,所以这时候的加载进物理内存的时候,实际内存中已经存在值,所以启动就会变快。

二、优化的方向

1.减少流程的数量。
2.减少每个流程的消耗。

三、APP的启动过程
四、启动时间分布

在Xcode中,可以通过设置环境变量来查看App的启动时间,DYLD_PRINT_STATISTICSDYLD_PRINT_STATISTICS_DETAILS


根据xcode打印可以看到:
动态库的加载

  • rebase/binding

    • 方法、类、Category这些需要rebase和 binding
    • binding
      Mach-O _DATA建立指针,指向外部函数,我们编译的时候,共享缓存库里边的代码指向的就是这里,链接的时候,通过dyld指向外部具体真实的地址。PIC技术
      fishook就是利用这个中转,hook的系统c函数。
  • Objc setup

    • 类的初始化过程在这里边
      ①读取二进制文件的 DATA 段内容,找到与 objc 相关的信息
      ②注册 Objc 类,ObjC Runtime 需要维护一张映射类名与类的全局表。当加载一个 dylib 时,其定义的所有的类都需要被注册到这个全局表中;
      ③读取 protocol 以及 category 的信息,把category的定义插入方法列表 (category registration),
      ④确保 selector 的唯一性
  • initializer
    load等函数,C++的构造函数

耗时比较多的
加载自己的库可能比较耗时

两个阶段分别是 pre- main 和 main启动到viewDidAppear:



pre-main过程:



什么是dyld?
dyld是一个专门用来加载动态链接库的库。

执行从dyld开始,dyld从可执行文件的依赖开始, 递归加载所有的依赖动态链接库。

五、 Dyld加载过程

加载Mach-O文件,加载一块比较大的内存空间,地址重定向
启动dyld的main函数

  • 配置xcode的环境变量 (arguments)
  • 加载共享缓存库,UIKit 和 Foundation这些

加载LoadCommons段一系列加载
加载动态库,包括自己的和系统的,实例化主程序的imageLoader,添加进imageList
链接主程序

开始初始化主程序
*runtime配合加载,runtime加载loadImages方法,这个方法就是prepare_load_methods(mh)
*加载load和C++构造函数
找main函数

配置(环境变量) — 加载(共享动态库,主程序,动态库) — 链接(链接主程序,动态库) — 初始化(程序)

  • 加载动态库过程,动态链接器dyld需要做如下操作
    1.分析依赖的动态库
    2.找到动态库的Mach-O文件
    3.打开文件
    4.验证文件
    5.在系统的内核注册文件签名
    6.对动态库的每个segment调用mmap()

针对这一步优化

1.减少非系统的动态库
2.使用静态库而不是动态库
3.合并非系统的动态库为一个

1.动态库尽可能少,不要多与6个,如果多了,尽可能合并
2.20000个类 800毫秒
3.swift静态语言,性能更高

六、ObjC SetUp

由于之前2步骤的优化,这一步实际上没有什么可做的。几乎都靠 Rebasing 和 Binding 步骤中减少所需 fix-up 内容。因为前面的工作也会使得这步耗时减少。

七、Initializers
  • 以上三步属于静态调整,都是在修改__DATA segment中的内容
    现在开始动态调整,在堆和栈中写入内容。 工作主要有:
    1、Objc+load()函数
    2、C++的构造函数属性函数 形如attribute((constructor)) void DoSomeInitializationWork()
    3、非基本类型的C++静态全局变量的创建(通常是类或结构体)(non-trivial initializer) 比如一个全局静态结构体的构建,如果在构造函数中有繁重的工作,那么会拖慢启动速度
    可以做的优化有:
    ①使用 +initialize 来替代 +load
    ②不要使用 atribute((constructor)) 将方法显式标记为初始化器,而是让初始化方法调用时才执行。 比如使用 dispatch_once(),pthread_once() 或 std::once()。也就是在第一次使用时才初始化,推迟了一部分工作耗时。 也尽量不要用到C++的静态对象。

从效率上来说,在+load+initialize里执行同样的代码,效率是一样的,即使有差距,也不会差距太大。 但所有的+load方法都在启动的时候调用,方法多了就会严重影响启动速度了。 就说我们项目中,有200个左右+load方法,一共耗时大概1s 左右,这块就会严重影响到用户感知了。 而+initialize方法是在对应 Class 第一次使用的时候调用,这是一个懒加载的方法,理想情况下,这200个+load方法都使用+initialize来代替,将耗时分摊到用户使用过程中,每个方法平均耗时只有5ms,用户完全可以无感知。

八、二进制重排

iOS的虚拟内存和物理内存:



之前所有应用都加载进内存条中,会有安全问题,会访问到其他人的软件中去

iOS虚拟内存有4G的

CPU通过 操作系统映射表 映射虚拟内存和物理真实内存
也就是我们访问虚拟内存,都会走到映射表,然后映射到真实的物理内存。(每个映虚拟内存对应一个映射表)

(mmp)分页加载,提高内存的利用率

内存是以字节为单位,内存以页管理 PAGE,Linux每1页4KB,iOS每一页16KB

用这种分页加载,用到哪块内存就加载到真实内存
上述P2加载的时候,发现真实内存没有,缺页,CPU会临时加载

如果启动的时候缺页比较多(缺页异常),就会不断去映射到物理内存,就会卡顿
iOS的内存地址,与编译顺序有关,所以如果方法在不同的类,类的加载差距比较大,那么就会加载比较多的页,就会浪费时间。
iOS系统还会对加载的每一页做签名认证,所以更加消耗时间。

system trace
Main Thread
Virtual Memory File Backard Page In

ldyd
order_file 自己写一个文件,里边指定方法的顺序,可以将启动要调用的方法,都放到里边。
libc-order在objc源码里边,这就是二进制重排

所有OC方法获取
用fishhook 去 HOOK objc_msgSend()方法,可以拿到所有的函数
可以用Clang插庒的方式AOP

八、main过程:
  • Main函数之后

1.懒加载
2.发挥CPU的价值,用多线程的方式。
3.框架的搭建尽量避免stroyboard 和 xib

  • 分阶段加载:

  • 在didFinishLunch中启动的:

    • 埋点功能、
    • Crash 采集
    • 网络配置等
  • 在首页viewDidappear

    • 初始化SDK,友盟SDK、人脸识别SDK后移
    • 开始定位(显示定位中),定位可以缓存,先加载,定位同步进行,如果缓存的地理位置不一样,然后才更新
    • 配置下发等
  • 多线程启动

尝试使用多线程启动
将数据库的配置、SDwebimage UA的配置放到子线程中初始化,引入状态管理,需要监控子线程任务状态,判断是否取消假的开屏页面。

广告同步加载,2秒回来了就展示,不回来就过
Timer Profile的使用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 背景 一个项目做的时间长了,启动流程往往容易杂乱,库也用的越来越多,APP的启动时间也会慢慢变长。本次将针对iOS...
    lp_lp阅读 1,558评论 0 12
  • 通过阅读这篇文章,我们将了解APP启动过程中都做了哪些事情。文章分为三部分,第一部分是原理讲解,第二部分是优化方案...
    CNWH阅读 895评论 0 4
  • App的启动可以分为2种: 冷启动(Cold Launch)从零开始启动APP 热启动(Warm Launch)A...
    Arthur澪阅读 693评论 0 0
  • 场景 假设一个这样的场景,早高峰赶公交,没带公交卡,掏出手机打开App1准备扫码上车,结果App半天进不去,后面的...
    寒江飄雪阅读 431评论 1 4
  • 1.App启动过程 解析Info.plist加载相关信息,例如如闪屏沙箱建立、权限检查 Mach-O加载如果是胖二...
    音符上的码字员阅读 1,298评论 0 4