WeSing iOS 启动优化 Old

2020-12-01  本文已影响0人  蓝天白云_Sam

1. 背景

冷启动时长是App性能的重要指标,冷启动的快慢直接影响着用户对App的第一印象.
随着版本的不断迭代,产品形态不断完善,业务功能日趋复杂.App里面的很多配置数据及接入的第三方SDK需要在冷启动期间完成,这给App的冷启动性能带来挑战.为此我们需要建立一套完整质量指标和体系来监测和持续优化冷启动性能.

2. 冷启动定义

大家一般把iOS冷启动过程定义为:从用户点击App图标开始到appDelegate didFinishLaunching方法执行完成为止。然而,当didFinishLaunchingWithOptions 执行完成时,用户还没有看到App的主界面,也不能对App进行操作。App还需要做一些初始化工作,首页渲染等过程后,用户才能真正看到数据内容并开始使用,我们认为这个时候冷启动才算完成。所以我们把App把冷启动过程定义为:从用户点击App图标开始到用户能看到App主界面内容为止这个过程,即T1+T2+T3:
App冷启动的3个阶段:

其中T1 又分以下几个阶段

image.png

3. 问题及现状

3.1 性能存量问题

问题 影响阶段 影响程度
业务层大量的启动任务 T2
第三方库的启动任务 T2+T3
一些比较耗时的主线程异步任务 T2+T3
同步I/O操作:数据库及NSUserDefaults T2+T3
请求数据,渲染(过程串行,时间较长 T3
大量的分类 T1
执行大量的+load()方法 T1
执行大量的+ (void)initialize方法 T1 + T2
加载无用的类,方法 T1

注:启动项的定义,在App启动过程中需要被完成的某项工作,我们称之为一个启动项。例如配置数据的加载,某个SDK的初始化、某个功能的预加载等。

3.2 性能增量问题:

一般情况下,在App早期阶段,冷启动不会有明显的性能问题。冷启动性能问题也不是在某个版本突然出现的,而是随着版本迭代,App功能越来越复杂,启动任务越来越多,冷启动时间也一点点延长。最后当我们注意到,并想要优化它的时候,这个问题已经变得很棘手了。我们App的性能问题增量主要来自启动项的增加,随着版本迭代,启动项任务简单粗暴地堆积在启动流程中。几个版本后,冷启动时长就会明显增加很多。

4. 治理思路

image.png

4.1 数据上报及监控,分析各阶段耗时

  1. 增加数据上报,监控各阶段耗时,及时发现新版本引入的性能问题.
  2. 客户端卡顿检测,对现网用户检测启动卡顿情况,及时发现问题.
  1. main 函数之前耗时
  2. didFinishLaunchingWithOptions 耗时
  3. 第一个viewDidLoad 耗时
  4. 第一个View渲染耗时: 第一个ViewWillAppear开始到第一个ViewDidAppear结束耗时
  5. App内启动耗时: 从main 函数开始到第一个ViewDidAppear结束总耗时
  6. App启动总耗时: 从点击App到第一个ViewDidAppear结束总耗时

4.2 规范启动流程

之前为了实现方便,在 didFinishLaunchingWithOptions, fireOnWnsLoginSucc,onLogInSucess中添加了大量的初始化任务,某些任务不是必须在启动过程中执行,只需要在首页渲染成功后再执行即可.

4.2.1 定义启动任务阶段

以前为了方便某些初始化工作,不停地在 didFinishLaunchingWithOptions, fireOnWnsLoginSucc,onLogInSucess里面添加任务,维护起来不方便,而且某些任务不是必须在启动过程中执行,只需要在首页渲染成功后再执行即可. 分析现有的各阶段调用:

  1. didFinishLaunchingWithOptions必定会在首页显示之前执行且只执行一次
  2. fireOnWnsLoginSucc,onLogInSucess有可能在首页显示前执行,也有可能在首页显示后执行.

我们需要重新定义各种启动任务的执行阶段,确定自己的任务需要在哪个阶段执行:注意:为了提高启动速度,任务尽可能地靠后执行
我们可以将任务执行阶段分为以下几个阶段:

  1. kMain: 在main函数开始执行执行
  2. kStartup: 在application:didFinishLaunchingWithOptions: 执行
  3. kAppeared: 在首页渲染完毕后执行
  4. kLogin: 在登录完成后执行
  5. kLoginEx: 在登录完成后且首页渲染完毕后执行

为此我们需要实现这样一种机制:

  1. 如果首页已经显示出来,任务立刻执行
  2. 如果首页尚未显示出来,则延后到首页显示出来后执行
    详情请参考runTaskAfterLaunchFinish

4.2.2 启动任务框架:不需要修改现有代码流程实现启动任务的方法

目前要在 didFinishLaunchingWithOptions, fireOnWnsLoginSucc,onLogInSucess添加任务,都需要在相应的函数里面添加代码,可扩展性和维护性不高(fireOnWnsLoginSucc,onLogInSucess可以通过注册相应通知来实现非嵌入添加任务,但是通知的方式性能比较差),我们可以利用Clang提供的编译器特性,在编译期将相关函数注册到二进制文件,在运行时读取相关配置来执行相关函数
通过该框架,我们可以很方便地添加一个在特定阶段执行的代码

//在application:didFinishLaunchingWithOptions阶段执行的代码
RUN_FUNCTION_ON_STAY(kStartup)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在main开始阶段执行的代码
RUN_FUNCTION_ON_STAY(kMain)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在首页渲染完毕后执行的代码
RUN_FUNCTION_ON_STAY(kAppeared)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在登录完成后执行的代码
RUN_FUNCTION_ON_STAY(kLogin)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在登录完成后且首页渲染完毕后执行
RUN_FUNCTION_ON_STAY(kLoginEx)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

4.3 代码规范

5. 优化目标:

6. 优化计划:

6.1 阶段1: 分析各阶段耗时分布,打点并上报,建立启动耗时各项指标(已完成)

  1. main 函数之前耗时
  2. didFinishLaunchingWithOptions 耗时
  3. 第一个viewDidLoad 耗时
  4. 第一个View渲染耗时: 第一个ViewWillAppear开始到第一个ViewDidAppear结束耗时
  5. App内启动耗时: 从main 函数开始到第一个ViewDidAppear结束总耗时
  6. App启动总耗时: 从点击App到第一个ViewDidAppear结束总耗时

6.2 阶段2: 优化耗时任务,非必要任务延后执行(已完成)

  1. 使用更高效的框架来替换使用广播通知来通知登录成功事件
  2. 定义各阶段启动任务并用更易于扩展和管理的方式添加启动任务
//在application:didFinishLaunchingWithOptions阶段执行的代码
RUN_FUNCTION_ON_STAY(kStartup)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在main开始阶段执行的代码
RUN_FUNCTION_ON_STAY(kMain)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在首页渲染完毕后执行的代码
RUN_FUNCTION_ON_STAY(kAppeared)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在登录完成后执行的代码
RUN_FUNCTION_ON_STAY(kLogin)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

//在登录完成后且首页渲染完毕后执行
RUN_FUNCTION_ON_STAY(kLoginEx)
{
    NSLog(@"File: %s,Line: %d",__FILE__,__LINE__);
}

6.3 阶段3: 优化IO操作(部分完成)

  1. 需要频繁访问的保存在磁盘的数据使用内存缓存:如NSUserDefaults或者数据库的数据做内存缓存
  2. 梳理高频访问的图片:如头像,作品,伴奏等的默认占位图,使用内存缓存
  3. 将配置从NSUserDefaults 迁移到更高效的数据库
  4. 提供配置类统一管理配置,将高频访问的配置合并保存

6.4 阶段4: 移除多余代码,减少PreMain耗时(部分完成)

6.5 代码重排,提高启动速度及整体运行速度

7. 耗时分布分析

启动过程主要耗时分布

从数据分析,耗时主要分布如下

  1. premain 耗时
  2. didFinishLaunch 耗时
  3. viewDidAppear 耗时: 首页渲染耗时,从viewWillAppearviewDidAppear之间的耗时
登录类型 preMain耗时 didFinishLaunch耗时 didBecomeActive耗时 viewDidLoad耗时 viewDidAppear耗时 启动App耗时 启动总耗时 count
有闪屏 781.75 1,294.32 22.75 0.32 287.15 21,397.12 22,178.87 5,340.00
非首次启动无粘贴板 870.70 1,325.54 17.32 0.20 851.25 2,436.70 3,307.40 1,323.00
有粘贴板有闪屏 914.31 1,396.95 22.54 0.28 369.90 6,891.93 7,806.24 569.00
非首次启动有粘贴板 1,221.50 1,616.51 18.78 0.30 730.98 2,664.43 3,885.94 113.00
首次启动无粘贴板 738.88 825.47 11.53 0.06 228.29 1,270.41 2,009.29 17.00
首次启动有粘贴板 1,684.71 1,267.43 7.14 0.43 262.43 1,943.57 3,628.29 7.00

didFinishLaunchingWithOptions 主要耗时分布

从数据分析,耗时主要分布如下

  1. enterMainUIWithLaunchOptions : 重点优化
  2. initWNSModule: 重点优化
  3. updateUserAgentWeSing :主要耗时在WKWebView触发的webkit模块加载逻辑,可优化成无需创建 WKWebView
  4. WSAuthSDKBase : 无优化空间,是否可以延后操作?
  5. pasteBoardStrings : 可延后处理
  6. initRDMCrashReportModule
函数 耗时
enterMainUIWithLaunchOptions 557.65
initWNSModule 446.73
updateUserAgentWeSing 66.91
WSAuthSDKBase 68.31
pasteBoardStrings 18.14
initRDMCrashReportModule 29.95
count 25.10
enterMainUIWithLaunchOptions 20.56
initWNSModule 15.67
updateUserAgentWeSing 19.66
WSAuthSDKBase 10.03
pasteBoardStrings 9.53

启动后优化

优化列表

启动前优化

上一篇 下一篇

猜你喜欢

热点阅读