天天看點

從iOS App啟動速度看如何為基礎性能保駕護航

作者:京東雲開發者

1 前言

啟動是App給使用者的第一印象,一款App的啟動速度,不單單是使用者體驗的事情,往往還決定了它能否擷取更多的使用者。是以到了一定階段App的啟動優化是必須要做的事情。App啟動基本分為以下兩種

1.1 冷啟動

App 點選啟動前,它的程序不在系統裡,需要系統新建立一個程序配置設定給它啟動的情況。這是一次完整的啟動過程。

表現:App第一次啟動,重新開機,更新等

1.2 熱啟動

App 在冷啟動後使用者将 App 退背景,在 App 的程序還在系統裡的情況下,使用者重新啟動進入 App 的過程,這個過程做的事情非常少。

是以我們主要說道說道冷啟動的優化

2 啟動流程

2.1 APP啟動都幹了什麼

要對啟動速度進行優化,我們需要知道啟動過程中的大緻流程是什麼,做了什麼事情,是否能針對性優化。

下圖是啟動流程的詳細分解

從iOS App啟動速度看如何為基礎性能保駕護航
  1. 點選圖示,建立程序
  2. mmap 主二進制,找到 dyld 的路徑
  3. mmap dyld,把入口位址設為_dyld_start

dyld 是啟動的輔助程式,是 in-process 的,即啟動的時候會把 dyld 加載到程序的位址空間裡,然後把後續的啟動過程交給 dyld。dyld 主要有兩個版本:dyld2 和 dyld3。

iOS 12之前主要是dyld2,iOS 13 開始 Apple 對三方 App 啟用了 dyld3,dyld3 的最重要的特性就是啟動閉包,閉包存儲在沙盒的 tmp/com.apple.dyld 目錄,清理緩存的時候切記不要清理這個目錄。

閉包裡主要有以下内容:

  • dependends,依賴動态庫清單
  • fixup:bind & rebase 的位址
  • initializer-order:初始化調用順序
  • optimizeObjc: Objective C 的中繼資料
  • 其他:main entry, uuid等等
從iOS App啟動速度看如何為基礎性能保駕護航

上圖虛線之上的部分是out-of-process的,在App下載下傳安裝和版本更新的時候會去執行,直接從緩存中讀取資料,加快加載速度

這些資訊是每次啟動都需要的,把資訊存儲到一個緩存檔案就能避免每次都解析,尤其是 Objective-C 的運作時資料(Class/Method…)解析耗時, 是以對啟動速度是一個優化提升

4.把沒有加載的動态庫 mmap 進來,動态庫的數量會影響這個階段

dyld從主執行檔案的header擷取到需要加載的所依賴動态庫清單,然後它需要找到每個 dylib,而應用所依賴的 dylib 檔案可能會再依賴其他 dylib,是以所需要加載的是動态庫清單一個遞歸依賴的集合

5.對動态庫集合循環load, mmap 加載到虛拟記憶體裡,對每個 Mach-O 做 fixup,包括 Rebase 和 Bind。

對每個二進制做 bind 和 rebase,主要耗時在 Page In,影響 Page In 數量的是 objc 的中繼資料

  • Rebase 在Image内部調整指針的指向。在過去,會把動态庫加載到指定位址,所有指針和資料對于代碼都是對的,而現在位址空間布局是随機化(ASLR),是以需要在原來的位址根據随機的偏移量做一下修正, 也就是說Mach-O 在 mmap 到虛拟記憶體的時候,起始位址會有一個随機的偏移量 slide,需要把内部的指針指向加上這個 slide.
  • Bind 是把指針正确地指向Image外部的内容。這些指向外部的指針被符号(symbol)名稱綁定,dyld需要去符号表裡查找,找到symbol對應的實作, 像 printf 等外部函數,隻有運作時才知道它的位址是什麼,bind 就是把指針指向這個位址,這也是後面我們能用fishhook來hook一些動态符号的核心

如下圖,編譯的時候,字元串 1234 在__cstring的 0x10 處,是以 DATA 段的指針指向 0x10。但是 mmap 之後有一個偏移量 slide=0x1000,這時候字元串在運作時的位址就是 0x1010,那麼 DATA 段的指針指向就不對了。Rebase 的過程就是把指針從 0x10,加上 slide 變成 0x1010。運作時類對象的位址已經知道了,bind 就是把 isa 指向實際的記憶體位址。

從iOS App啟動速度看如何為基礎性能保駕護航

6.初始化 objc 的 runtime,由于閉包已經初始化了大部分,這裡隻會注冊 sel 和裝載 category

7.+load 和靜态初始化被調用,除了方法本身耗時,這裡可能還會引起大量 Page In,如果調用了dispatch_async則會延遲啟動後的runloop開啟後執行,如果觸發靜态初始化,則會延遲到運作時執行

8.初始化 UIApplication,啟動 Main Runloop,可以在之前章節利用runloop統計首屏耗時,也可以在啟動結束做一些預熱任務

9.執行 will/didFinishLaunch,這裡主要是業務代碼耗時。首頁的業務代碼都是要在這個階段,也就是首屏渲染前執行的,主要包括了:首屏初始化所需配置檔案的讀寫操作;首屏清單大資料的讀取;首屏渲染的大量計算等;sdk的初始化;對于大型元件化工程,也包含了很多moudle的啟動附加元件

10.Layout,viewDidLoad 和Layoutsubviews 會在這裡調用,Autolayout 太多會影響這部分時間

11.Display,drawRect 會調用

12.Prepare,圖檔解碼發生在這一步

13.Commit,首幀渲染資料打包發給 RenderServer,走GPU渲染流水線流程,啟動結束

(tips: 2.2.10-2.2.13這裡主要是圖形渲染流水線的部分流程,Application産生圖元階段(CPU階段))。後續會交由單獨的RenderServer程序,再調用渲染架構(Metal/OpenGL ES)來生成 bitmap,放到幀緩沖區裡,硬體根據時鐘信号讀取幀緩沖區内容,完成螢幕重新整理

2.2 啟動各階段時長統計

上一小節對啟動各個階段過程的詳細闡述,歸納起來大緻分為6個階段(WWDC2019):

從iOS App啟動速度看如何為基礎性能保駕護航

通過對各個階段進行時長統計分析,進行優化然後對比。

可以在Xcode中設定環境變量DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS看下啟動階段和對應的耗時(iOS15後環境變量失效)

也可以通過Xcode MetricKit 本身也可以看到啟動耗時:打開 Xcode -> Window -> Origanizer -> Launch Time

如果公司有對應的成熟監控體系最好,這裡我們主要通過手動無侵入埋點去統計啟動時長,對啟動流程pre main-> after main進行統計分析

2.1.1 程序建立時間打點

通過 sysctl 系統調用拿到程序建立的時間戳

#import <sys/sysctl.h>
#import <mach/mach.h>


+ (BOOL)processInfoForPID:(int)pid procInfo:(struct kinfo_proc*)procInfo
{
    int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
    size_t size = sizeof(*procInfo);
    return sysctl(cmd, sizeof(cmd)/sizeof(*cmd), procInfo, &size, NULL, 0) == 0;
}


+ (NSTimeInterval)processStartTime
{
    struct kinfo_proc kProcInfo;
    if ([self processInfoForPID:[[NSProcessInfo processInfo] processIdentifier] procInfo:&kProcInfo]) {
        return kProcInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + kProcInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0;
    } else {
        NSAssert(NO, @"無法取得程序的資訊");
        return 0;
    }           

2.1.2 main()執行時間打點

// main之前調用
// pre-main()階段結束時間點:__t2
void static __attribute__ ((constructor)) before_main()
{
  if (__t2 == 0)
  {
    __t2 = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970;
  }
}           

2.1.3 首屏渲染時間打點

啟動的終點對應使用者感覺到的 Launch Image 消失的第一幀

iOS 12 及以下:root viewController 的 viewDidAppear

iOS 13+:applicationDidBecomeActive

Apple 官方的統計方式是第一個 CA::Transaction::commit,但對應的實作在系統架構内部,不過我們可以找到最接近這個的時間點

通過 Runloop 源碼分析和調試,我們發現 CFRunLoopPerformBlock,kCFRunLoopBeforeTimers 和 CA::Transaction::commit()為最近的時間點,是以在這裡打點即可.

具體就是可以通過在 didFinishLaunch 中向 Runloop 注冊 block 或者 BeforeTimer 的 Observer 來擷取這兩個時間點的回調,代碼如下:

注冊block:

//注冊block
CFRunLoopRef mainRunloop = [[NSRunLoop mainRunLoop] getCFRunLoop];
CFRunLoopPerformBlock(mainRunloop,NSDefaultRunLoopMode,^(){
    NSTimeInterval stamp = [[NSDate date] timeIntervalSince1970];
    NSLog(@"runloop block launch end:%f",stamp);
});           

監聽BeforeTimer 的 Observer

//注冊kCFRunLoopBeforeTimers回調
CFRunLoopRef mainRunloop = [[NSRunLoop mainRunLoop] getCFRunLoop];
CFRunLoopActivity activities = kCFRunLoopAllActivities;
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, activities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    if (activity == kCFRunLoopBeforeTimers) {
        NSTimeInterval stamp = [[NSDate date] timeIntervalSince1970];
        NSLog(@"runloop beforetimers launch end:%f",stamp);
        CFRunLoopRemoveObserver(mainRunloop, observer, kCFRunLoopCommonModes);
    }
});
CFRunLoopAddObserver(mainRunloop, observer, kCFRunLoopCommonModes);           

綜上分析現有項目版本啟動時間均值:

[函數名:+[LaunchTrace mark]_block_invoke][行号:54]—————App啟動————-耗時:pre-main:4.147820

[函數名:+[LaunchTrace mark]_block_invoke][行号:55]—————App啟動————-耗時:didfinish:0.654687

[函數名:+[LaunchTrace mark]_block_invoke][行号:56]—————App啟動————-耗時:total:4.802507

3 啟動優化

上節我們主要分析了App啟動流程和時長統計,下面就是我們要優化的方向,盡可能對各個階段進行優化,當然也不是過度優化,項目不同階段、不同規模相應的問題會不一樣,做針對性分析優化.

3.1 Pre Main 優化

3.1.1 調整動态庫

檢視了現有工程,基本都以動态庫進行連結,總計48個,是以思路如下

  • 減少動态庫,自有動态庫轉靜态庫
  • 現有的庫是以CocoaPods管理的,是以通過hook pod建構流程修改Xcode config将部分pod的Mach-O type改為Static Library;
  • 同時對一些代碼較大的動态庫進行ROI分析,分析是否可以不依賴,在代碼内即可實作替代邏輯,這樣删除一些ROI很低的動态庫
  • 合并動态庫
  • 目前項目引入的動态庫較為簡單,不存在合并項,對于有些中大型工程,有很多自己的基建UI庫,很多過于分散,需要做的就是能聚合就聚合,譬如XXTableView, XXHUD, XXLabel,建議合并成一個XXUIKit;譬如一些工具庫,也可以根據實際情況聚合為一個
  • 動态庫懶加載
  • 經過分析目前項目階段規模還沒必要進行懶加載動态庫,畢竟優化要考慮收益,僅做優化思路參考
  • 正常動态庫都是會被主二進制直接或者間接連結的,那麼這些動态庫會在啟動的時候加載。如果隻打包進 App,不參與連結,那麼啟動的時候就不會自動加載,在運作時需要用到動态庫裡面的内容的時候,再手動懶加載
  • 運作時通過-[NSBundle load]來加載,本質上調用的是底層的 dlopen。

3.1.2 rebase&binding&objc setup階段

  • 無關的Class、Method的符号加載耗時也會帶來額外的啟動耗時;是以我們要減少__DATA段中的指針數量;對項目代碼分析發現很多類似的Category,每個Category裡面可能隻有一個功能函數,是以具體根據項目情況分析進行Category合并
從iOS App啟動速度看如何為基礎性能保駕護航
從iOS App啟動速度看如何為基礎性能保駕護航
從iOS App啟動速度看如何為基礎性能保駕護航
  • +load 除了方法本身的耗時,還會引起大量 Page In,另外 +load 的存在對 App 穩定性也是沖擊,因為 Crash 了捕獲不到。
  • 項目中不少類似以下load函數邏輯,具體分析後很多可以作為啟動器進行治理管理,runloop空閑去執行,
  • 首屏後延時加載
從iOS App啟動速度看如何為基礎性能保駕護航
從iOS App啟動速度看如何為基礎性能保駕護航
  • 另外一類是load邏輯操作:很多元件化通訊解耦方案之一就是在load函數内做協定和類的綁定,這部分可以利用 clang attribute,将其遷移到編譯期:
typedef struct{
    const char * cls;
    const char * protocol;
}_di_pair;
#if DEBUG
#define DI_SERVICE(PROTOCOL_NAME,CLASS_NAME)\
__used static Class<PROTOCOL_NAME> _DI_VALID_METHOD(void){\
    return [CLASS_NAME class];\
}\
__attribute((used, section(_DI_SEGMENT "," _DI_SECTION ))) static _di_pair _DI_UNIQUE_VAR = \
{\
_TO_STRING(CLASS_NAME),\
_TO_STRING(PROTOCOL_NAME),\
};\
#else
__attribute((used, section(_DI_SEGMENT "," _DI_SECTION ))) static _di_pair _DI_UNIQUE_VAR = \
{\
_TO_STRING(CLASS_NAME),\
_TO_STRING(PROTOCOL_NAME),\
};\
#endif           

原理很簡單:宏提供接口,編譯期把類名和協定名寫到二進制的指定段裡,運作時把這個關系讀出來就知道協定是綁定到哪個類了。

  • 下線代碼

無用代碼删除在所有的性能優化手段裡基本上是ROI最低的。但是幾乎所 有ROI較高的技術手段都是一次性優化方案,經過幾個版本疊代後再做優化就會比較乏力。相比之下,針對代碼的檢測和删除在很長的一段時間内提供了很大的優化空間

檢測手段:靜态掃描Mach-O檔案對classlist和classrefs做差集,形成初步的無用類集合,并根據業務代碼特征做二次适配

當然還有其他常用的技術手段包括AppCode工具檢測以及以例如Pecker這樣的基于 IndexStoreDB 、線上統計等。

不過以上方案對Swift的檢測方案不太适用(和OC存儲差異),這裡可以參考github.com/wuba/WBBlad…

對項目進行檢測,發現還是很多無用類的:

從iOS App啟動速度看如何為基礎性能保駕護航

然後二次分析驗證,進行優化

3.1.3 二進制重排

iOS系統中虛拟記憶體到實體記憶體的映射都是以頁為最小機關的。當程序通路一個虛拟記憶體Page而對應的實體記憶體卻不存在時,就會出現Page Fault缺頁中斷,(對應System Trace的File Backed Page In) 然後作業系統把資料加載到實體記憶體中,如果已經已經加載到實體記憶體了,則會觸發Page Cache Hit,後者是比較快的,這也是熱啟動比冷啟動快的原因之一。

雖然缺頁中斷異常這個處理速度是很快的,但是在一個App的啟動過程中可能出現上千(甚至更多)次Page Fault,這個時間積累起來會比較明顯了。

基于上面原理. 我們的目标就是在啟動的時候增加Page Cache Hit,減少Page Fault,進而達到優化啟動時間的目的

我們需要确定,在啟動的時候,執行了哪些符号,盡可能讓這些符号的記憶體集中在一起,減少占用的頁數,就能減少Page Fault的命中次數

程式預設情況下是順序執行的:

從iOS App啟動速度看如何為基礎性能保駕護航

如果啟動需要使用的方法分别在2頁Page1和Page2中(method1和method3),為了執行相應的代碼,系統就必須進行兩個Page Fault。

如果我們對方法進行重新排列,讓method1和method3在一個Page,那麼就可以較少一次Page Fault。

從iOS App啟動速度看如何為基礎性能保駕護航

通過Instruments中的System Trace工具來看下目前的page fault加載情況

從iOS App啟動速度看如何為基礎性能保駕護航

這裡有個注意點,為了確定App是真正的冷啟動,需要把記憶體清幹淨,不然結果會不太準,下圖是我直接殺掉App,重新打開得到的結果

從iOS App啟動速度看如何為基礎性能保駕護航

可以看到,和第一次測試差的有點多,我們可以在殺掉App後,重新打開多個其他的App(盡可能多),或者解除安裝重裝,這樣在重新打開App的時候,就會冷啟動

綜上我們要做的就是将啟動時調用的函數符号集中靠前排列,減少缺頁中斷數量

  • 擷取啟動代碼執行順序
  • 确定App在啟動的時候,調用了哪些函數(使用了哪些符号),這裡推薦一個工具AppOrderFiles(https://github.com/yulingtianxia/AppOrderFiles ),使用Clang SanitizerCoverage,通過編譯器插裝的方式,擷取到調用函數的符号順序(當然我們也可以在Build Settings中修改Write Link Map File為YES編譯後會生成一個Link Map符号表txt,進行分析,建立我們自己的order檔案)在App啟動後,到首屏VC的viewDidLoad方法内輸出order file。

輸出的檔案在App沙盒,用模拟器運作更友善,得到檔案app.order,這裡面就是排好序的符号清單,根據App的執行順序,如果項目比較大的話,會比較久.

把order檔案放到工程目錄,配置到Xcode裡面Build Setting -> Order File -> $(PROJECT_DIR)/xxx.order

從iOS App啟動速度看如何為基礎性能保駕護航
  • 驗證\對比

    Xcode裡面Build Setting有個Write Link Map File,可以生成Link Map檔案的選項,路徑如下

Link Map檔案

Intermediates.noindex/xxxx.build/Debug-iphoneos/xxx.build/xxx-LinkMap-normal-arm64.txt

生成app檔案路徑

Products/Debug-iphoneos/xxx.app

這裡我們隻關注Link Map File的符号表Symbols,這裡的順序就是Mach-O檔案對應的順序,如果與xxx.order的順序一緻,就表明改成功了

再次通過System Trace工具測試修改前後對比

從iOS App啟動速度看如何為基礎性能保駕護航

優化前後對比,缺頁中斷明顯減少

擷取函數調用符号,采用Clang插樁可以直接hook到Objective-C方法、Swift方法、C函數、Block,可以不用差別對待

3.2 After Main優化

這部分是個大頭的優化項,實際場景需要我們根據自己的具體項目來分析,但大體遵循一些相同的思路

3.2.1 功能/方法優化

  • 推遲&減少I/O操作
  • 此處對項目after main後的啟動邏輯分析不涉及IO操作未做優化
  • 控制線程數量
  • 項目中啟動階段線程數量不多且必要,影響不大就未動,但根據各自的項目情況進行分析治理
  • 啟動附加元件治理
  • 這裡主要是一些基建和三方/集團SDK初始化任務以及各業務元件工程的啟動附加元件, 包括前面部分load函數的邏輯放到這裡的啟動器來進行排程管理。
  • 我們可以把這部分做一個啟動器進行維護和監控,防劣化。
  • 啟動器自注冊,注冊項包括啟動操作閉包,啟動執行優先級,啟動操作是否背景執行等可選項。
  • 自注冊服務無非還是:”啟動項:啟動閉包 “ 這麼一個綁定實作,是以可以類似前面(class-protocol綁定)所講的思路,将這部分操作寫入到可執行檔案的DATA段中,運作時再從DATA段取出資料進行相應的操作(調用函數),這樣也能夠覆寫所有的啟動階段,例如main()之前的階段。
  • 對項目分析後,将鍵盤初始化、地圖定位、意見回報還有非首頁子產品初始化等非必要的啟動項降低優先級延後時機執行。
  • 串行->并行 同步->異步
  • 對于一些耗時操作異步、并行操作,不阻塞主線程的執行
  • 方法耗時統計分析
  • 統計啟動過程業務代碼耗時并對耗時方法進行分析治理
  • 高頻次方法調用
  • 有些方法的單個耗時不高,但是頻繁調用就會顯現耗時,我們可以加記憶體緩存,當然了具體場景具體分析
  • 利用閃屏頁的時間做一些首頁UI的預建構
  • 項目中有啟動閃屏頁,還有第一次啟動彈框隐私頁這個間隙做一些首屏操作的前移
從iOS App啟動速度看如何為基礎性能保駕護航
  • 利用這一段時間來建構首頁UI了、首屏網絡資料的預下載下傳、緩存、啟動Flutter引擎等工作

3.2.2 首屏渲染優化

螢幕顯示遵循一套圖形渲染管線來完成最終的顯示工作:

1.Application階段(應用内):

Handle Events:

這個過程中會先處理點選事件,這個過程中有可能會需要改變頁面的布局和界面層次。

Commit Transaction:

此時 App 會通過 CPU 處理顯示内容的前置計算,比如布局計算、圖檔解碼等任務,之後将計算好的圖層進行打包發給 Render Server。(核心Core Animation負責)

Commit Transaction 這部分中主要進行的是:Layout、Display、Prepare、Commit 等四個具體的操作, 最後形成一條事務,通過 CA::Transaction::commit()送出渲染

  • Layout:

建構視圖相關,layoutSubviews、addSubview 方法添加子視圖、AutoLayout根據 Layout Constraint 計算各個view的frame,文本計算(size)等。

layoutSubviews:在此階段會調用,但是滿足條件如frame,bounds,transform屬性改變、添加或者删除view、顯式調用setNeedsLayout等

  • Display:

繪制視圖:交給 Core Graphics 進行視圖的繪制,得到圖元 primitives 資料,注意不是位圖資料,位圖是GPU階段根據圖元組合而得。但是如果重寫了 drawRect: 方法,這個方法會直接調用 Core Graphics 繪制方法得到 bitmap 資料,同時系統會額外申請一塊記憶體,用于暫存繪制好的 bitmap,導緻繪制過程從 GPU 轉移到了 CPU,這就導緻了一定的效率損失。與此同時,這個過程會額外使用 CPU 和記憶體,是以需要高效繪制,否則容易造成 CPU 卡頓或者記憶體爆炸。

  • Prepare:

Core Animation 額外的工作,主要是圖檔解碼和轉換,盡量使用GPU支援的格式, Apple推薦JPG和PNG

譬如在UIImageView中展示圖檔,會經曆如下過程: 加載、解碼、渲染 簡單說就是将普通的二進制資料 (存儲在dataBuffer 資料) 轉化成 RGB的資料(存儲在ImageBuffer), 這個被稱為圖像的解碼decode, 它有如下特點:

decode解碼過程是一個耗時過程, 并且是在CPU中完成的. 也就是我們這部分的prepare中完成。

解碼以後的RGB圖占用的記憶體大小隻與bitmap的像素格式(RGB32, RGB23, Gray8 …)和圖檔寬高有關, 常見bitmap大小: 每個像素點大小 width height, 而與原來的壓縮格式PNG, JPG大小無關.

2.GPU渲染階段:

主要是一些圖元的操作、幾何處理、光栅化、像素處理等,不一一細說,這部分操作我們能做的工作畢竟是有限的

是以,我們大緻可以做的優化點如下:

  • 預渲染\異步渲染:
  • 大緻思路就是在子線程将所有的視圖繪制成一張位圖,然後回到主線程指派給 layer的 contents
  • 圖檔異步解碼:
  • 注意這裡并不是将圖檔加載放到異步線程中在異步線程中生成一個 UIImage或者是 CGImage然後再主線程中設定給 UIImageView,而是在子線程中先将圖檔繪制到CGBitmapContext,然後從bitmap 直接建立圖檔,常用的圖檔架構都類似。
  • 按需加載
  • 不需要或者非首屏較為複雜的視圖延後加載,減少首屏圖層的層級
  • 其他:
  • 離屏渲染 盡量減少透明視圖個數等等一些細節也要注意

4 成果

經過一些列優化,還是有一些速度的提升,雖然工程還不是大型工程,不過及早持續優化可以防止業務疊代到一定程度難以下手的地步。

iPhone 7p多次均值

優化前

[函數名:+[LaunchTrace mark]_block_invoke][行号:54]—————App啟動————-耗時:pre-main:4.147820

[函數名:+[LaunchTrace mark]_block_invoke][行号:55]—————App啟動————-耗時:didfinish:0.654687

[函數名:+[LaunchTrace mark]_block_invoke][行号:56]—————App啟動————-耗時:total:4.802507

優化後

[函數名:+[LaunchTrace mark]_block_invoke][行号:54]—————App啟動————-耗時:pre-main:3.047820

[函數名:+[LaunchTrace mark]_block_invoke][行号:55]—————App啟動————-耗時:didfinish:0.254687

[函數名:+[LaunchTrace mark]_block_invoke][行号:56]—————App啟動————-耗時:total:3.302507

pre main階段下降平均大概20%, after main階段平均下降大概60%, 總體均值下降30%.

當然目前還處于未上線版本,後續上線後借助監控平台借助線上更多資料,更多機型來更好的的進行分析優化

5 總結

啟動速度瓶頸非一日之寒,需要持續的進行優化,這當中也少不了監控體系的持續建設和優化,日常線上資料的分析,防止業務快速疊代中的啟動速度劣化,對動态庫的引入、新增 +load 和靜态初始化、啟動任務的新增都要加入Code Review機制,優化啟動架構為啟動這些基礎性能保駕護航。

作者:京東物流 彭欣

來源:京東雲開發者社群 自猿其說Tech

繼續閱讀