App启动优化 - 实践二

概要:

  • 什么是启动?
  • 如何测量启动?
  • 使用Instruments分析启动
  • 跟踪启动的进度

一、启动的重要性

  • 影响用户的体验
  • 表明代码的整体性能
  • 影响系统性能和电池

二、启动类型

  • 冷启动(Cold)
    当重启设备或App长时间未启动(App不在内存中)时,则会发生冷启动。
    为了启动App,需要将它从磁盘读入内存,启动支持App的系统服务,然后生成App的进程。
  • 热启动(Warm)
    当App发生一次启动之后,再次启动就是热启动。此时App程序部分在内存中。
  • 继续运行(Resume)
    当App从主屏幕,或App切换器重新进入自己的App时,则发生这种情况。此时App程序完全在内存中。

冷启动、热启动、运行之间的区别:

三、启动过程

image.png

这六个过程包含了从系统初始化到App初始化,再到视图创建和布局,若App有需要,还可能会有一个扩展阶段,用于数据的异步加载。

(一)System Interface

第一阶段System Interface的前半部是DYLD3 ,关于DYLD3可以参考资料 App Startup Time: Past, Present, and Future

  • 静态链接器会加载App的共享库和框架;
  • 为热启动缓存运行时的依赖项;

第一阶段System Interface的后半部是libSystem Init。在App中初始化底层系统组件的时候,现在这主要是系统方面的工作,有固定的消耗。因此我们不需要关注这一部分。

优化建议:

  • 避免链接未使用的框架;
  • 避免在启动期间加载动态库;
    例如:dlopen()、或NSBundle、或load(),因为这样会损失在缓存中建立的那些优势
  • 硬链接所有的依赖;
(二)Static Runtime Initialization

第二阶段是静态运行时初始化,该阶段主要完成以下工作:

  • 系统初始化Objective-C和Swift语言运行时;
  • 调用所有类的静态加载方法;

一般而言我们的App不应该在这里做任何工作,除非我们的程序中存在静态初始化方法,或者我们链接的框架带来的。通常不建议静态初始化。

优化建议:

  • 暴露框架中的初始化API;
  • 避免使用+[Class load]方法,减少对启动的影响;
  • 使用+[Class initialize]方法延迟静态初始化;
    如果程序中有一个使用静态初始化的框架,则要考虑暴露API来尽早初始化我们的栈。如果必须要使用静态初始化,请考虑将代码移出+[Class load]。因为+load方法在App启动期间总会被调用。我们可以在类中第一次使用方法时来延迟调用。
(三) UIKit Initialization

第三阶段是UIKit初始化,该阶段的工作如下:

  • 系统初始化UIApplicationUIApplicationDelegate
  • 开始事件的处理和系统的集成;

该阶段是系统初始化程序UIApplicationUIApplicationDelegate的时候。在大多数情况下,这是系统的工作,设置事件处理和系统的集成。如果我们在子类UIApplication,或者在UIApplicationDelegate初始化程序中做其他工作,仍然会影响这一阶段。

优化建议:

  • 减少在子类UIApplication中工作;
  • 减少在UIApplicationDelegate初始化中的工作;
(四)Application Initialization

第四阶段是App初始化,最重要的东西都在这里,该阶段的工作如下:

  • 调用UIApplicationDelegate的App生命周期回调;

这是作为开发者能够对App启动产生重大影响的地方。如果我们的App还没有采用UISceneAPI,或针对iOS 12及更早版本的用户来说,App初始化仍然可以用这些回调方法。

application:willFinishLaunchingWithOptions:
application:didFinishLaunchingWithOptions:
  • 调用UIApplicationDelegate的UI生命周期回调;

当App展示给用户时,将会进一步调用下面的方法。

applicationDidBecomeActive:
  • 为每个场景调用UISceneDelegate的UI生命周期回调

当我们的App没有采用UISceneAPI时,我们应该在application:didFinishLaunchingWithOptions:方法中创建视图控制器。当使用UIScene时,App初始化的工作方式略有不同。我们仍然可以获得application:willFinishLaunchingWithOptions:application:didFinishLaunchingWithOptions:方法,但是当App展示给用户时,我们将获得UISceneDelegate生命周期回调。

scene:willConnectToSession:options:
sceneWillEnterForeground:
sceneDidBecomeActive:

优化建议:

  • 推迟无关的工作;
    但没必要提交你的第一帧,可以通过将其推送到后台队列,或者稍后再完全执行。
  • 在场景之间共享资源;
    如果我们的App采用了UIScene,要确保在场景之间共享资源。这样做是为了减少多次不必要地进行一些工作的开销。
(五) Frame Render

第五阶段是第一帧渲染,这个阶段相对简单,该阶段的工作如下:

  • 创建视图,执行布局,绘制视图;
loadView 
viewDidLoad 
layoutSubviews
  • 提交和渲染第一帧;

优化建议:

  • 展平视图层次结构和延迟加载视图;
    我们可以减少层次结构中的视图数量来影响次阶段。也可以通过展平视图来减少使用,或延迟加载在启动期间未显示的视图来实现。
  • 优化自动布局的使用;
    查看自动布局,减少正在使用的约束数量;
(六)Extended

第六阶段是扩展,该阶段的工作如下:

  • 第一帧后App特定时间段;
  • 显示异步加载的数据;
  • 应用程序应具有交互性和响应性;

这是从我们第一次提交到向用户显示最后帧的App特定时间段。这是当我们加载异步数据时。其实不是每个App都有这个阶段。如果我们App有这个阶段,那么我们的App应该具有交互性和响应性。

优化建议:

  • 利用os_signpost衡量工作;
    当我们的应用确实有这个阶段时,我们需要了解正在发生什么,并且可以通过利用os_signpostApi来标记和衡量在这两个时期发生的工作。

四、如何正确测量启动

(一) 一致性
  • 消除差异来源以产生更一致的结果
  • 可能导致启动时间不具有代表性
  • 使用一致的结果评估进度
image.png
image.png
(二)在一个干净一致的环境中测试

通过以下方法,可以保证创建一个干净一致的测试环境:

  • 重启设备,并让系统休眠2-3分钟;

重启设备可以清除不必要的状态,让系统休眠2-3分钟,来清除任何启动时间工作。

  • 打开飞行模式,或模拟网络;

减少对网络的依赖。因为网络会引入相当多的差异。

  • 使用不变的iCloud帐户和数据,或完全注销iCloud帐户;

iCloud在后台运行。这回干扰App的启动。

  • 使用应用的发布版本;

这是为了减少测量期间不必需要的调试代码的开销,并利用编译时优化。

  • 测量热启动;

这是因为它们更加一致,因为某些App可能已经在内存中,并且其中一些系统端服务可能已在运行。

(三)用具有代表性数据进行测试
image.png
(四)测试较新和较旧的设备
image.png
(五)使用XCTest测量启动
image.png

在任何给定的时间,iOS设备都处于各种不同的状态和情况下,这可能会在启动时引起很大的差异。因此当我们分析和比较启动结果时,确保我们进行"Apple - To - App le"的比较是至关重要的。因为如果在进行任何更改之前,你的启动结果完全不可预测。我们如何知道自己是否取得了进展呢?使其可预测的第一步是消除这些差异的来源,例如网络干扰,后台进程中的干扰。现在我们意识到这听起来有悖常理,因为这可能会导致启动不能完全代表常规使用。但这没有关系,拥有一致的结果可以评估很好地进展,这一点尤为重要。在Apple中一直只用这种技术,在开发过程中成功检测回归,并缩短启动时间。然后通过使用在实际情况中收集的遥测数据来验证这些性能的改进。

五、如何优化启动

当我们在代码和工具中查看App的启动时,我们应该记住以下三个提示和技巧:

(一)最小化你的工作
  • 推迟与第一帧无关的工作;
    推迟未显示的视图,或尚未使用的预加热功能等内容。

  • 避免阻塞主线程;
    比如网络I/O,文件I/O或其他。这些都会影响启动,可以将其移动到后台线程。

  • 减少内存的使用;
    分配和操作内存可能需要时间。

(二)优先考虑你的工作
(三)优化你的工作
  • 简化或限制现有工作
    例如限制仅在启动期间获取所需数据的数据量,或者懒计算所需的任何变量和结果
  • 优化算法和数据结构
  • 缓存资源和计算
    我们应该缓存资源和复杂的功能,减少CPU和内存开销。

六、跟踪启动的进度

  • 使用Xcode Organizer监视用户的启动;
image.png
  • 采用MetricKit获取更多的统计信息;

七、参考资料

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

推荐阅读更多精彩内容