APP啟動(dòng)優(yōu)化

通過(guò)閱讀這篇文章,我們將了解APP啟動(dòng)過(guò)程中都做了哪些事情擂送。文章分為三部分悦荒,第一部分是原理講解,第二部分是優(yōu)化方案嘹吨,第三部分是實(shí)踐應(yīng)用搬味。

第一部分 原理講解

APP啟動(dòng)的分類(lèi)

APP分為冷啟動(dòng)和熱啟動(dòng)。

冷啟動(dòng)是指蟀拷, App 點(diǎn)擊啟動(dòng)前碰纬,它的進(jìn)程不在系統(tǒng)里,需要系統(tǒng)新創(chuàng)建一個(gè)進(jìn)程分配給它啟動(dòng)的情況问芬。這是一次完整的啟動(dòng)過(guò)程悦析。

熱啟動(dòng),對(duì)于熱啟動(dòng)的理解存在分歧此衅,一種是是指戶點(diǎn)擊home鍵后再次回到前臺(tái)顯示强戴,還原到退出前的狀態(tài),繼續(xù)為用戶服務(wù)炕柔;另一種是用戶殺掉APP進(jìn)程后在dyld沒(méi)有刪除緩存的情況下重啟APP酌泰,這種情況下的啟動(dòng)速度會(huì)比冷啟動(dòng)要快一些。

對(duì)于熱啟動(dòng)的分歧匕累,筆者覺(jué)得從優(yōu)化角度來(lái)說(shuō)陵刹,第二種根據(jù)有研究的意義和符合實(shí)際使用情況。對(duì)比兩個(gè)啟動(dòng)方式對(duì)用戶的影響欢嘿,冷啟動(dòng)的快慢會(huì)給用戶造成第一印象的好壞衰琐,影響用戶的使用體驗(yàn)和留存也糊。接下來(lái)我們深入研究下APP冷啟動(dòng)過(guò)程。

從點(diǎn)擊到啟動(dòng)

當(dāng)用戶點(diǎn)擊手機(jī)桌面上的圖標(biāo)到首頁(yè)展示到用戶面前并且可以進(jìn)行交互羡宙,這個(gè)過(guò)程我們定義為APP啟動(dòng)的一個(gè)完整過(guò)程狸剃。這個(gè)過(guò)程中發(fā)生了很多事情。系統(tǒng)先讀取App的可執(zhí)行文件(Mach-O文件)狗热,從里面獲得dyld的路徑钞馁,然后加載dyld,dyld去初始化運(yùn)行環(huán)境匿刮,開(kāi)啟緩存策略僧凰,加載程序相關(guān)依賴庫(kù)(其中也包含我們的可執(zhí)行文件),并對(duì)這些庫(kù)進(jìn)行鏈接熟丸,最后調(diào)用每個(gè)依賴庫(kù)的初始化方法训措,在這一步,runtime被初始化光羞。當(dāng)所有依賴庫(kù)的初始化后绩鸣,輪到最后一位(程序可執(zhí)行文件)進(jìn)行初始化,在這時(shí)runtime會(huì)對(duì)項(xiàng)目中所有類(lèi)進(jìn)行類(lèi)結(jié)構(gòu)初始化纱兑,然后調(diào)用所有的load方法呀闻。最后dyld返回main函數(shù)地址,main函數(shù)被調(diào)用萍启,我們便來(lái)到了熟悉的程序入口总珠。

image.png

我們將以上階段分為Ready、Pre-Main勘纯、Main三個(gè)階段。

Ready階段

iPhone/iPad的桌面系統(tǒng)也是一個(gè)應(yīng)用钓瞭,我們稱之為Springboard驳遵,當(dāng)用戶觸碰屏幕點(diǎn)擊APP的icon時(shí),XNU加載Mach-O和dyld山涡,然后系統(tǒng)會(huì)從XNU內(nèi)核態(tài)將控制權(quán)轉(zhuǎn)移到dyld用戶態(tài)堤结。dyld會(huì)負(fù)責(zé)后續(xù)工作,這時(shí)第一個(gè)階段完成鸭丛。


image.png

為了精簡(jiǎn)文章體積和側(cè)重本文重點(diǎn)竞穷,我們簡(jiǎn)單了解幾個(gè)概念:Mach-Odyld ,有興趣的同學(xué)可以自行深入學(xué)習(xí)和研究鳞溉。

Mach-O

Mach-O是Mach object的縮寫(xiě)瘾带,是Mac\iOS上用于存儲(chǔ)程序、庫(kù)的標(biāo)準(zhǔn)格式熟菲。

Mach-O有以下幾種類(lèi)型看政,其實(shí)動(dòng)態(tài)庫(kù)朴恳、靜態(tài)庫(kù)、程序本身允蚣、bundle文件其實(shí)都是屬于Mach-O文件于颖。


image.png

既然是文件,就會(huì)有固定的存儲(chǔ)格式:Header嚷兔、Load Commands森渐、Raw Data三大部分。

  • Header:保存了一些基本信息冒晰,包括了該文件運(yùn)行的平臺(tái)章母、文件類(lèi)型、LoadCommands的個(gè)數(shù)等等翩剪。

  • LoadCommands:可以理解為加載命令乳怎,在加載Mach-O文件時(shí)會(huì)使用這里的數(shù)據(jù)來(lái)確定內(nèi)存的分布以及相關(guān)的加載命令。比如我們的main函數(shù)的加載地址前弯,程序所需的dyld的文件路徑蚪缀,以及相關(guān)依賴庫(kù)的文件路徑。

  • Raw Data: 這里包含了具體的代碼恕出、數(shù)據(jù)等等询枚。

image.png

dyld

dyld是動(dòng)態(tài)鏈接器。dyld從可執(zhí)行文件的依賴開(kāi)始, 遞歸加載所有的依賴動(dòng)態(tài)鏈接庫(kù)浙巫。系統(tǒng)內(nèi)核在加載動(dòng)態(tài)庫(kù)前金蜀,會(huì)加載dyld,然后調(diào)用去執(zhí)行__dyld_start()的畴,該函數(shù)會(huì)執(zhí)行dyldbootstrap::start()渊抄,后者會(huì)執(zhí)行_main()函數(shù),dyld的加載動(dòng)態(tài)庫(kù)的代碼就是從_main()開(kāi)始執(zhí)行的丧裁。

dyld存放位置目前版本存放的路徑是/usr/lib/dyld护桦。系統(tǒng)會(huì)解析Mach-O文件中的Load Commands段的LC_LOAD_DYLINKER,獲取到dyld的路徑(如下圖)煎娇。


image.png

根據(jù)這一階段的具體工作二庵,我們沒(méi)有優(yōu)化空間,準(zhǔn)備工作完全有系統(tǒng)來(lái)處理缓呛。

pre-main階段

當(dāng)dyld執(zhí)行到_main()函數(shù)時(shí)催享,它將加載程序所需要的動(dòng)態(tài)庫(kù),對(duì)其進(jìn)行rebase以及bind操作哟绊,然后運(yùn)行初始化函數(shù)因妙,執(zhí)行程序的main函數(shù)。


image.png

我們將以上分為L(zhǎng)oad dylibs image、Rebase image兰迫、Bind image信殊、Objc、setup initializers五個(gè)階段汁果,接下來(lái)我們看一下每個(gè)階段都做了什么工作涡拘。


image.png
  • Load dylibs image階段

將Mach-O中的代碼段和數(shù)據(jù)段加載到虛擬內(nèi)存后,dyld從主執(zhí)行文件的 header 獲取到需要加載的所依賴動(dòng)態(tài)庫(kù)列表据德,找到對(duì)應(yīng)的dylib后確保它是Mach-O文件鳄乏,接著找到代碼簽名并將其注冊(cè)到內(nèi)核中。應(yīng)用所依賴的 dylib 文件可能會(huì)再依賴其他 dylib棘利,所以 dyld 所需要加載的是動(dòng)態(tài)庫(kù)列表一個(gè)遞歸依賴的集合橱野。一般應(yīng)用會(huì)加載 100 到 400 個(gè) dylib 文件,但大部分都是系統(tǒng) dylib善玫,它們會(huì)被預(yù)先計(jì)算和緩存起來(lái)水援,加載速度很快。(下圖是一個(gè)APP的加載動(dòng)態(tài)庫(kù)的情況茅郎,在lldb下通過(guò)image命令查看)

image.png

動(dòng)態(tài)庫(kù)的加載分為兩種類(lèi)型蜗元,一種是系統(tǒng)動(dòng)態(tài)庫(kù),一種是自定義的動(dòng)態(tài)庫(kù)系冗。針對(duì)系統(tǒng)動(dòng)態(tài)庫(kù)奕扣,蘋(píng)果幫我們做了很好的優(yōu)化,從iOS3.1開(kāi)始掌敬,為了提高性能惯豆,絕大部分的系統(tǒng)動(dòng)態(tài)庫(kù)文件都打包存放到了一個(gè)緩存文件中,那就是動(dòng)態(tài)共享緩存dsc(dyld shared cache)奔害,dsc存在的位置在/System/Library/Caches/com.apple.dyld/dyld_shared_cache_armX,X代表的是ARM處理器指令集架構(gòu)楷兽,APP啟動(dòng)前會(huì)通過(guò)dyld在des中查找然后遞歸加載所有依賴的動(dòng)態(tài)庫(kù)到內(nèi)存中。(dyld源碼下載地址https://opensource.apple.com/tarballs/dyld/)

對(duì)于自定義動(dòng)態(tài)庫(kù)的使用舀武,我們有很大的優(yōu)化空間拄养,減少自定義動(dòng)態(tài)庫(kù)的使用數(shù)量,體積和引用關(guān)系银舱,都有助于縮短這個(gè)階段的耗時(shí)。

  • Rebase image階段

為了防止程序被輕易的惡意篡改跛梗,iOS4.3后蘋(píng)果提出了ASLR的方案寻馏,每個(gè)程序有自己的虛擬內(nèi)存,所有的虛擬內(nèi)存都是從0x00000000開(kāi)始的核偿,使用ASLR后诚欠,程序加載VM Address會(huì)有一個(gè)偏移量,這個(gè)偏移量是每次首次加載程序時(shí)隨機(jī)產(chǎn)生,然后在這個(gè)偏移量的基礎(chǔ)上繼續(xù)加載Pagezero區(qū)域(0x100000000)轰绵,之后的內(nèi)容就是順序有序加載粉寞。那么這個(gè)過(guò)程就是為了針對(duì)mach-o在加載到內(nèi)存中不是固定的首地址(ASLR)這一現(xiàn)象做數(shù)據(jù)修正的過(guò)程;由于ASLR(address space layout randomization)的存在左腔,可執(zhí)行文件和動(dòng)態(tài)鏈接庫(kù)在虛擬內(nèi)存中的加載地址每次啟動(dòng)都不固定唧垦,所以需要這2步來(lái)修復(fù)鏡像中的資源指針,來(lái)指向正確的地址液样。 rebase修復(fù)的是指向當(dāng)前鏡像內(nèi)部的資源指針振亮; 而bind指向的是鏡像外部的資源指針。

  • Bind image

將指針指向鏡像外部的內(nèi)容鞭莽,binding就是將這個(gè)二進(jìn)制調(diào)用的外部符號(hào)進(jìn)行綁定的過(guò)程坊秸。比如我們objc代碼中需要使用到NSObject, 即符號(hào)OBJC_CLASS$_NSObject,但是這個(gè)符號(hào)又不在我們的二進(jìn)制中澎怒,在系統(tǒng)庫(kù) Foundation.framework 中褒搔,因此就需要binding這個(gè)操作將對(duì)應(yīng)關(guān)系綁定到一起;Binding 看起來(lái)計(jì)算量比 Rebasing 更大喷面,但其實(shí)需要的 I/O 操作很少星瘾,Binding的時(shí)間主要是耗費(fèi)在計(jì)算上,因?yàn)镮O操作之前 Rebasing 已經(jīng)替 Binding 做過(guò)了乖酬,所以這兩個(gè)步驟的耗時(shí)是混在一起的死相。

可以從查看 __DATA 段中需要修正(fix-up)的指針,所以減少指針數(shù)量才會(huì)減少這部分工作的耗時(shí)咬像。對(duì)于 ObjC 來(lái)說(shuō)就是減少 Class,selector 和 category 這些元數(shù)據(jù)的數(shù)量算撮。從編碼原則和設(shè)計(jì)模式之類(lèi)的理論都會(huì)鼓勵(lì)大家多寫(xiě)精致短小的類(lèi)和方法,并將每部分方法獨(dú)立出一個(gè)類(lèi)別县昂,其實(shí)這會(huì)增加啟動(dòng)時(shí)間肮柜。

Objective-C 中有很多數(shù)據(jù)結(jié)構(gòu)都是靠 Rebasing 和 Binding 來(lái)修正(fix-up)的,比如 Class 中指向父類(lèi)的指針和指向方法的指針倒彰。Rebase&&Binding該階段的優(yōu)化關(guān)鍵在于減少__DATA segment中的指針數(shù)量。

  • Objc setup

在這一階段待讳,dyld會(huì)讀取二進(jìn)制文件的DATA段內(nèi)容芒澜,找到與objc相關(guān)的信息;注冊(cè) Objc 類(lèi)创淡,ObjC Runtime 需要維護(hù)一張映射類(lèi)名與類(lèi)的全局表痴晦。當(dāng)加載一個(gè) dylib 時(shí),其定義的所有的類(lèi)都需要被注冊(cè)到這個(gè)全局表中琳彩;讀取 protocol 以及 category 的信息誊酌,把category的定義插入方法列表 (category registration)部凑,確保 selector 的唯一性。

對(duì)于這一階段碧浊,我們能做的基本沒(méi)有涂邀,完全依賴上一步的優(yōu)化而減少耗時(shí)。

  • initializers

虛擬內(nèi)存動(dòng)態(tài)庫(kù)后邊存放的是堆區(qū)和棧區(qū)箱锐。這一階段的工作就是在這兩個(gè)區(qū)域展開(kāi)寫(xiě)入比勉。具體內(nèi)容是dyld開(kāi)始將程序二進(jìn)制文件初始化;交由ImageLoader讀取image瑞躺,其中包含了我們的類(lèi)敷搪、方法等各種符號(hào),由于runtime向dyld綁定了回調(diào)幢哨,當(dāng)image加載到內(nèi)存后赡勘,dyld會(huì)通知runtime進(jìn)行處理;runtime接手后調(diào)用mapimages做解析和處理捞镰,接下來(lái)loadimages中調(diào)用 callloadmethods方法闸与,遍歷所有加載進(jìn)來(lái)的Class,按繼承層級(jí)依次調(diào)用Class的+load方法和其 Category的+load方法岸售。執(zhí)行完后便會(huì)進(jìn)入main()階段践樱。

針對(duì)這一階段的優(yōu)化方案也是比較明顯。就是針對(duì)+load進(jìn)行處理凸丸,網(wǎng)上比較統(tǒng)一的答案是使用+initialize來(lái)替代+load的使用拷邢。這個(gè)方案的使用當(dāng)然是可以的,但是要注意邏輯的調(diào)用時(shí)機(jī)是否復(fù)合你的研發(fā)需求屎慢,還有就是需要注意做一些代碼執(zhí)行次數(shù)的保護(hù)瞭稼。

main階段

main() 函數(shù)執(zhí)行的階段,指的是從 main() 函數(shù)執(zhí)行開(kāi)始腻惠,到 appDelegate 的 didFinishLaunchingWithOptions 方法里首屏渲染相關(guān)方法執(zhí)行完成壹堰。

執(zhí)行到這一階段說(shuō)明dyld已經(jīng)進(jìn)入了程序的main()函數(shù)入口校套,UIApplicationMain回調(diào)代理方法didFinishLaunchingWithOptions狮斗。這里沒(méi)有需要優(yōu)化的地方亭罪,我們要做的就是處理didFinishLaunchingWithOptions方法執(zhí)行開(kāi)始到結(jié)束的這段時(shí)間的工作。

開(kāi)發(fā)者會(huì)把各種初始化工作都放到這個(gè)階段執(zhí)行欣喧,導(dǎo)致渲染完成滯后腌零。更加優(yōu)化的開(kāi)發(fā)方式,應(yīng)該是從功能上梳理出哪些是首屏渲染必要的初始化功能唆阿,哪些是 App 啟動(dòng)必要的初始化功能莱没,而哪些是只需要在對(duì)應(yīng)功能開(kāi)始使用時(shí)才需要初始化的。梳理完之后酷鸦,將這些初始化功能分別放到合適的階段進(jìn)行。

二、優(yōu)化方案

針對(duì)pre-main和main兩個(gè)階段臼隔,我們可以總結(jié)出以下的方法:

  • pre-main階段
  1. 減少自定義動(dòng)態(tài)庫(kù)的依賴嘹裂;
  2. 合并多個(gè)自定義動(dòng)態(tài)庫(kù)為一個(gè)動(dòng)態(tài)庫(kù);
  3. 減少Objc類(lèi)的數(shù)量摔握,減少selector數(shù)量寄狼,刪除無(wú)用類(lèi)和函數(shù)(包括分類(lèi)),如果有必要可以嘗試合并一些類(lèi)氨淌;
  4. 減少一些無(wú)用的靜態(tài)變量泊愧;
  5. 減少C++虛函數(shù)的數(shù)量;
  6. 合理的+initializers替代+load的使用盛正;
  7. 盡量不要用到C++的靜態(tài)對(duì)象删咱;
  8. 類(lèi)名和方法名不宜過(guò)長(zhǎng),iOS每個(gè)類(lèi)和方法名都在__cstring段里都存了相應(yīng)的字符串值豪筝,所以類(lèi)和方法名的長(zhǎng)短也是對(duì)可執(zhí)行文件大小是有影響的痰滋。
  • main階段
  1. 優(yōu)化代碼邏輯,去除一些非必要的邏輯和代碼续崖,減少每個(gè)流程所消耗的時(shí)間敲街;
  2. 減少啟動(dòng)初始化流程,酌情將一些初始化工作延后严望;
  3. 使用多線程來(lái)處理初始化工作多艇;
  4. 使用純代碼來(lái)構(gòu)建tabbar或nav等視圖,減少因?yàn)閤ib和storyboard解析成代碼帶來(lái)的消耗像吻。

三峻黍、實(shí)踐應(yīng)用

以下測(cè)試數(shù)據(jù)均來(lái)自我司線下版,不代表線上產(chǎn)品性能萧豆。

pre-main階段

main()之前的加載時(shí)間如何衡量奸披,蘋(píng)果提供了一種檢測(cè)pre-main階段的方法:DYLD_PRINT_STATISTICS = 1。


image.png

為了驗(yàn)證我們上文所說(shuō)的信息涮雷,我們分別做了兩次對(duì)比阵面,

//冷啟動(dòng)-1
Total pre-main time: 3.3 seconds (100.0%)
         dylib loading time: 273.38 milliseconds (8.1%)
        rebase/binding time: 163.78 milliseconds (4.8%)
            ObjC setup time: 363.07 milliseconds (10.8%)
           initializer time: 2.5 seconds (76.0%)
           slowest intializers :
             libSystem.B.dylib :  19.49 milliseconds (0.5%)
    libMainThreadChecker.dylib :  92.20 milliseconds (2.7%)
          libglInterpose.dylib : 164.11 milliseconds (4.9%)
         libMTLInterpose.dylib :  69.42 milliseconds (2.0%)
       美術(shù)寶1對(duì)1線下版 : 2.4 seconds (72.1%)

//冷啟動(dòng)-2
Total pre-main time: 3.3 seconds (100.0%)
         dylib loading time: 249.00 milliseconds (7.4%)
        rebase/binding time: 167.79 milliseconds (4.9%)
            ObjC setup time: 404.63 milliseconds (12.0%)
           initializer time: 2.5 seconds (75.5%)
           slowest intializers :
             libSystem.B.dylib :  16.98 milliseconds (0.5%)
    libMainThreadChecker.dylib :  88.08 milliseconds (2.6%)
          libglInterpose.dylib : 173.89 milliseconds (5.1%)
         libMTLInterpose.dylib :  70.84 milliseconds (2.1%)
       美術(shù)寶1對(duì)1線下版 : 2.4 seconds (72.2%)

//熱啟動(dòng)-1
Total pre-main time: 1.8 seconds (100.0%)
         dylib loading time:  89.18 milliseconds (4.9%)
        rebase/binding time: 168.99 milliseconds (9.3%)
            ObjC setup time: 131.04 milliseconds (7.2%)
           initializer time: 1.4 seconds (78.4%)
           slowest intializers :
             libSystem.B.dylib :  19.17 milliseconds (1.0%)
    libMainThreadChecker.dylib :  58.62 milliseconds (3.2%)
          libglInterpose.dylib : 161.28 milliseconds (8.9%)
         libMTLInterpose.dylib :  42.63 milliseconds (2.3%)
                  ZegoLiveRoom :  39.11 milliseconds (2.1%)
       美術(shù)寶1對(duì)1線下版 : 1.2 seconds (70.3%)

//熱啟動(dòng)-2
Total pre-main time: 1.3 seconds (100.0%)
         dylib loading time: 172.99 milliseconds (12.9%)
        rebase/binding time:  40.79 milliseconds (3.0%)
            ObjC setup time:  63.74 milliseconds (4.7%)
           initializer time: 1.0 seconds (79.2%)
           slowest intializers :
             libSystem.B.dylib :  11.00 milliseconds (0.8%)
    libMainThreadChecker.dylib :  41.56 milliseconds (3.1%)
          libglInterpose.dylib :  99.05 milliseconds (7.3%)
         libMTLInterpose.dylib :  49.45 milliseconds (3.6%)
       美術(shù)寶1對(duì)1線下版 : 1.0 seconds (74.9%)

因?yàn)榭紤]到dyld緩存的原因,所以針對(duì)冷啟動(dòng)的測(cè)試洪鸭,我會(huì)重啟設(shè)備后進(jìn)行打印样刷,熱啟動(dòng)的測(cè)試是在冷啟動(dòng)后我們殺掉應(yīng)用后間隔幾秒鐘再次開(kāi)啟應(yīng)用。測(cè)試結(jié)果也是比較明顯和直觀览爵,我司APP冷啟動(dòng)時(shí)的pre-main階段大約耗時(shí)3.3秒置鼻。這是個(gè)什么概念?老牛拉車(chē)蜓竹。

以下是WWDC2016 Apple給出的建議

Apple suggest to aim for a total app launch time of under 400ms and you must do it in less than 20 seconds or the system will kill your app.

Apple建議應(yīng)用的啟動(dòng)時(shí)間控制在400ms之下箕母。并且必須在20s以內(nèi)完成啟動(dòng)储藐,否則系統(tǒng)則會(huì)kill掉應(yīng)用程序。

從范圍400ms到20s可以看出嘶是,我們優(yōu)化的空間還蠻大钙勃,因?yàn)楫吘褂袀€(gè)底線400ms在嘛。分析一下數(shù)據(jù)聂喇,可以看出前四個(gè)階段的占比不高辖源,24%大約就是790ms左右,占比最大的其實(shí)是第五個(gè)階段(intializers階段)希太。

先從前四個(gè)階段入手克饶,看一下目前APP啟動(dòng)時(shí)大約用到了多少動(dòng)態(tài)庫(kù),


image.png

大約485個(gè)誊辉,其中有一些動(dòng)態(tài)庫(kù)是相同的地址矾湃,這個(gè)工程中的大部分哭都是來(lái)自des系統(tǒng)庫(kù),其中有兩個(gè)目錄下的文件是來(lái)自于我們自己的Mach-O芥映,DoraemonLoadAnalyze和KSYMediaPlayer洲尊,第一個(gè)是滴滴的性能檢測(cè)庫(kù),只有在線下版這種target下會(huì)進(jìn)行打包奈偏,KSYMediaPlayer是用于視頻播放的一個(gè)庫(kù)坞嘀,線上版和線下版都有存在,這個(gè)庫(kù)是沒(méi)有辦法去掉惊来。那么我們?cè)谕瑯拥木€下版環(huán)境下進(jìn)行以下對(duì)比測(cè)試丽涩,去掉第一個(gè)動(dòng)態(tài)庫(kù)試一試。得到的結(jié)果不是很理想裁蚁,這也在情理之中矢渊,結(jié)果還是3.3s左右。這個(gè)也是在情理之中的事情枉证,四百多個(gè)庫(kù)不會(huì)因?yàn)?兩個(gè)庫(kù)的額減少而出現(xiàn)明顯變化矮男。

接下來(lái)來(lái)處理下第五個(gè)階段,lldb幫我們打印出了最慢的幾個(gè)因素室谚。

- libSystem.B.dylib
- libMainThreadChecker.dylib
- libglInterpose.dylib
- libMTLInterpose.dylib
- 美術(shù)寶1對(duì)1線下版

根據(jù)之前的學(xué)習(xí)毡鉴,這個(gè)階段的大部分內(nèi)容是runtime在幫你處理初始化相關(guān)的工作。前四個(gè)因素我們沒(méi)有優(yōu)化的點(diǎn)秒赤,接著從美術(shù)寶1對(duì)1線下版這個(gè)Mach-O文件處理猪瞬,對(duì)其中所有重寫(xiě)了+load方法的地方我們重新處理看看效果。
主程序中重寫(xiě)了+load的類(lèi)大約有21個(gè)文件左右入篮,將這些工作延后處理陈瘦,得到以下測(cè)試數(shù)據(jù):

//冷啟動(dòng)
Total pre-main time: 2.9 seconds (100.0%)
         dylib loading time: 407.79 milliseconds (13.7%)
        rebase/binding time: 142.19 milliseconds (4.7%)
            ObjC setup time: 370.54 milliseconds (12.4%)
           initializer time: 2.0 seconds (68.9%)
           slowest intializers :
             libSystem.B.dylib :  20.20 milliseconds (0.6%)
    libMainThreadChecker.dylib :  85.84 milliseconds (2.8%)
          libglInterpose.dylib : 153.29 milliseconds (5.1%)
         libMTLInterpose.dylib :  73.55 milliseconds (2.4%)
       美術(shù)寶1對(duì)1線下版 : 1.9 seconds (65.1%)

//熱啟動(dòng)
Total pre-main time: 1.2 seconds (100.0%)
         dylib loading time: 174.79 milliseconds (13.4%)
        rebase/binding time:  38.48 milliseconds (2.9%)
            ObjC setup time:  55.36 milliseconds (4.2%)
           initializer time: 1.0 seconds (79.2%)
           slowest intializers :
             libSystem.B.dylib :  10.56 milliseconds (0.8%)
    libMainThreadChecker.dylib :  41.27 milliseconds (3.1%)
          libglInterpose.dylib : 111.59 milliseconds (8.6%)
         libMTLInterpose.dylib :  32.40 milliseconds (2.5%)
       美術(shù)寶1對(duì)1線下版 : 966.81 milliseconds (74.6%)

由于熱啟動(dòng)的數(shù)據(jù)根據(jù)緩存的因素耗時(shí)上下浮動(dòng),但是冷啟動(dòng)的數(shù)據(jù)比較穩(wěn)定潮售,通過(guò)對(duì)比可以看出我們的優(yōu)化還是很有效果的痊项,從整體啟動(dòng)時(shí)間3.3s下降到了2.9s锅风,其中主程MachO的耗時(shí)從2.4下降到了1.9s。到目前這里线婚,我們還可以繼續(xù)根據(jù)上面的方案繼續(xù)優(yōu)化pre-main這個(gè)階段的內(nèi)容遏弱,但是由于美術(shù)寶的項(xiàng)目過(guò)于龐大,加之組件化的加入塞弊,這里靠一人之力就不去嘗試測(cè)試了。筆者猜測(cè)這部分雖然還有優(yōu)化的空間泪姨,但是鑒于優(yōu)化方案的操作是減少類(lèi)名稱和方法名稱長(zhǎng)度游沿,從理論上來(lái)判斷應(yīng)該是通過(guò)減小體積來(lái)縮小IO時(shí)間,所以能繼續(xù)縮短多少時(shí)間肮砾,筆者猜也未必會(huì)太多诀黍,但是從數(shù)據(jù)來(lái)看耗時(shí)較大的地方是主包,所以從C++虛函數(shù)和構(gòu)造函數(shù)仗处、靜態(tài)變量眯勾、刪除無(wú)用類(lèi)來(lái)看的話,應(yīng)該還是有很可觀的優(yōu)化空間的婆誓。

main階段

這個(gè)階段我們主要從didFinishLaunchingWithOptions方法中進(jìn)行優(yōu)化吃环,首屏VC的初始化我們暫不考慮。代碼打點(diǎn)工具有很多洋幻,我們這里使用BLStopwatch郁轻,使用方法比較簡(jiǎn)單,記錄一個(gè)起始時(shí)間文留,再記錄一個(gè)截止時(shí)間好唯。我們先看一下優(yōu)化前的數(shù)據(jù):

//冷啟動(dòng)
15:45:33.171686+0800 美術(shù)寶1對(duì)1線下版[227:4773] 初始化耗時(shí):3.158380s

//熱啟動(dòng)
15:49:14.153892+0800 美術(shù)寶1對(duì)1線下版[237:6115] 初始化耗時(shí):2.000703s

如果你是用戶,新下載了APP燥翅,打開(kāi)耗時(shí)3.3+3.1s,你會(huì)怎么想骑篙?
可能是我自己的手機(jī)該換了。

讓我們來(lái)分析一下這個(gè)didFinishLaunchingWithOptions中到底做了什么事情森书,哪些事情是可以放到子線程中執(zhí)行靶端。

#1 神策完成: 0.753s
#2 防止崩潰完成: 1.112s
#3 bugly完成: 0.106s
#4 日志服務(wù)器hook完成: 0.020s
#5 httpDNS完成: 0.020s
#6 startLaunchInit完成: 0.649s
#7 開(kāi)啟APP額外初始化完成: 0.486s
#8 JPush初始化完成: 0.024s
#9 引導(dǎo)完成: 0.004s
  • 首先看一下神策SDK的初始化,這個(gè)耗時(shí)相對(duì)真?zhèn)€階段的整體耗時(shí)來(lái)說(shuō)占了四分之一左右拄氯,筆者細(xì)細(xì)看了下神策開(kāi)發(fā)文檔躲查,其中提示要求在主線程初始化,并且咨詢了需求點(diǎn)是用來(lái)埋點(diǎn)統(tǒng)計(jì)使用译柏,所以無(wú)法將其放到子線程或延時(shí)初始化镣煮,目前也只能放到這里暫不修改。

  • 防崩潰初始化工作鄙麦,這個(gè)地方耗時(shí)1.112s典唇,其中主要工作是進(jìn)行一些類(lèi)的方法交換镊折,時(shí)間全浪費(fèi)在這個(gè)主線程處理一些與UI無(wú)關(guān)的工作上了,我么將其滯后處理介衔,放到+initialize中處理恨胚。

  • 關(guān)于bugly這部分的初始化工作,不知道會(huì)不會(huì)和神策的需求有冗余炎咖,如果有的話可以嘗試去掉一個(gè)赃泡,bugly目前的耗時(shí)不是很多0.106s左右,看了bugly的文檔沒(méi)有提示說(shuō)要在主線程乘盼,但是對(duì)于這種統(tǒng)計(jì)性的SDK升熊,筆者認(rèn)為還是放到這里不要?jiǎng)恿耍讓訖C(jī)制透明的绸栅,我們無(wú)法預(yù)料放到其他線程或延時(shí)初始化有沒(méi)有影響级野。

  • 日志服務(wù)器hook初始化和httpDNS的工作這里耗時(shí)0.02s+0.02s,蒼蠅雖小仍是肉粹胯,也不能放過(guò)優(yōu)化蓖柔,看了內(nèi)部實(shí)現(xiàn),果斷放到了子線程中處理风纠。

  • startLaunchInit和引導(dǎo)和UI相關(guān)况鸣、極光推送的內(nèi)容放主線程中暫不處理。

以下是修改后的數(shù)據(jù):

#1 神策完成: 0.756
#2 防崩潰完成: 0.001
#3 bugly完成: 0.132
#4 日志服務(wù)器hook完成: 0.000
#5 httpDNS完成: 0.000
#6 startLaunchInit完成: 0.627
#7 初始化啟動(dòng)頁(yè)面完成: 0.408
#8 JPush完成: 0.042
#9 引導(dǎo)獲取完成: 0.021

//冷啟動(dòng)
16:45:33.171686+0800 美術(shù)寶1對(duì)1線下版[227:4773] 初始化耗時(shí):1.958380s

到目前為止項(xiàng)目?jī)?yōu)化啟動(dòng)時(shí)間大約1.5s左右议忽。啟動(dòng)優(yōu)化除了受技術(shù)本身影響還和你的業(yè)務(wù)需求息息相關(guān)懒闷。這部分工作我們還有很大的提升空間,文章中如有問(wèn)題歡迎校正栈幸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末愤估,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子速址,更是在濱河造成了極大的恐慌玩焰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芍锚,死亡現(xiàn)場(chǎng)離奇詭異昔园,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)并炮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)默刚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人逃魄,你說(shuō)我怎么就攤上這事荤西。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵邪锌,是天一觀的道長(zhǎng)勉躺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)觅丰,這世上最難降的妖魔是什么饵溅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮妇萄,結(jié)果婚禮上蜕企,老公的妹妹穿的比我還像新娘。我一直安慰自己嚣伐,他們只是感情好糖赔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著轩端,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逝变。 梳的紋絲不亂的頭發(fā)上基茵,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音壳影,去河邊找鬼拱层。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宴咧,可吹牛的內(nèi)容都是我干的根灯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼掺栅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼烙肺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起氧卧,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桃笙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后沙绝,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體搏明,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年闪檬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了星著。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粗悯,死狀恐怖虚循,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤邮丰,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布行您,位于F島的核電站,受9級(jí)特大地震影響剪廉,放射性物質(zhì)發(fā)生泄漏娃循。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一斗蒋、第九天 我趴在偏房一處隱蔽的房頂上張望捌斧。 院中可真熱鬧,春花似錦泉沾、人聲如沸捞蚂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)姓迅。三九已至,卻和暖如春俊马,著一層夾襖步出監(jiān)牢的瞬間丁存,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工柴我, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留解寝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓艘儒,卻偏偏與公主長(zhǎng)得像聋伦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子界睁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353