乾貨 | Trip.com APP 啓動優化實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"引言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"啓動是用戶對App的第一印象,對於用戶體驗尤爲重要,所以我們花了很多時間在啓動時間的優化上。本文將分享Trip.com App的啓動優化實踐,從分析App啓動的過程開始,在瞭解啓動流程的基礎上制定大的優化原則和小的具體方案,希望能對大家有所幫助。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、App啓動的流程分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"想做啓動優化,首先要了解清楚啓動的各個流程,然後才能對各個環節去做針對性措施。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"借用WWDC對啓動階段的定義圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ce\/cea0e917c4380ae7dc613e8cb63f7b63.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.1 System Interface"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加載App可執行文件"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Load dylibs"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加載動態鏈接器"},{"type":"codeinline","content":[{"type":"text","text":"dyld"}]},{"type":"text","text":" ,"},{"type":"codeinline","content":[{"type":"text","text":"dyld"}]},{"type":"text","text":"會遞歸加載App依賴的動態庫,然後執行符號綁定"},{"type":"codeinline","content":[{"type":"text","text":"Rebase"}]},{"type":"text","text":", "},{"type":"codeinline","content":[{"type":"text","text":"Bind"}]},{"type":"text","text":"。一般應用會加載 100 到 400 個 dylib 文件,幸運是大部分是系統庫,且系統會在操作系統啓動時計算和緩存系統動態庫。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Apple爲了解決安全問題,引入"},{"type":"codeinline","content":[{"type":"text","text":"ASLR"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"Code Sign"}]},{"type":"text","text":",如果不作符號修正,程序將沒法正常運行,所以會有Rebase和Bind過程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/f0\/f0572ce87eb87cb3dda7b1ca67af8e80.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Rebase"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在鏡像內部調整指針的指向,其實就是將內部指針都加上偏移量(Slide=實際新地址-舊地址)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Bind"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"修正指外部的指針,比如上圖中malloc,這個符號不存在於我們App的Mach-O中,需要從外部的鏡像中獲取,這時候就需要Bind操作把這個關聯起來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"libSystem init"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用系統的一些初始化方法,這部分一般時間比較固定,可以不用太關注。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.2 Runtime Init"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Objc和Swift的初始化"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過"},{"type":"codeinline","content":[{"type":"text","text":"_dyld_objc_notify_register"}]},{"type":"text","text":"註冊回調,在image加載完時初始化語言相關。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加載category"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面語言初始化完之後,會加載所有category,處理category的所有方法,協議和屬性等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用所有+load"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也是通過向 dyld 註冊回調,在image加載完時,通過"},{"type":"codeinline","content":[{"type":"text","text":"load_images"}]},{"type":"text","text":" 觸發,處理該image相關的所有+load方法,按照繼承層級依次調用:父類+load→子類+load→category +load,注意category的+load不會覆蓋原類。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用C++的構造函數屬性函數 "},{"type":"codeinline","content":[{"type":"text","text":"attribute((constructor))"}]},{"type":"text","text":" "}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3 UIKit Init"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實例化 UIApplication 和 UIApplicationDelegate"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開始事件處理和系統集成"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.4 Application Init"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這部分是我們熟悉的 UIApplicationDelegate 的幾個生命週期調用:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"application:willFinishLaunchingWithOptions:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"application:didFinishLaunchingWithOptions:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"applicationDidBecomeActive:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"scene:willConnectToSession:options:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"sceneWillEnterForeground:"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"sceneDidBecomeActive:"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.5 Initial Frame Render"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏是App渲染第一幀,主要做了創建、佈局和繪製視圖的工作,並把準備好的第一幀提交給渲染層渲染。這裏面佈局計算,圖片解碼,圖層樹的遞歸commit到Render Server等都是可能影響耗時的點,所以要特別注意。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.6 Extended"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏按照蘋果的定義,是異步獲取數據展示界面的邏輯。比如我們首頁要從網絡請求數據然後展示最新數據在頁面上。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、針對啓動的各個流程我們能做什麼"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1 總體原則"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不管哪個流程,我們都想盡量遵循下面兩個原則:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"刪"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"刪的原則是指,對App啓動和運行不是必須的任務,或者跟首頁渲染第一幀無關的任務,都從啓動流程中刪除。對於刪除的任務,可以進行懶加載的形式,需要時再調用;也可以換到其他的時機去觸發,比如首頁渲染完之後。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"壓"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"壓的原則是指,對App啓動和運行必須的任務,或者直接影響首頁渲染第一幀的任務,都儘可能壓縮其運行時間。至於做法,可以是優化方法內的實現,使其運行更快;也可以將方法執行的線程切換到子線程,以併發的形式降低其對整個啓動過程的影響。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2 具體方案"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.1 減少動態庫"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"動態庫的加載在啓動階段是必須的,所以我們要儘量減少非必要的動態庫。對此我們做了以下幾點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)梳理所有動態庫,將用不到的或者可以簡單替代的動態庫刪除"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以通過"},{"type":"codeinline","content":[{"type":"text","text":"otool -L xxx.app\/xxx"}]},{"type":"text","text":" 或者打開打包後的產物,從xxx.app\/Frameworks路徑中找到所有動態庫,逐個篩選,將其中可以廢棄和替代的動態庫刪除。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)通過推進社區(第三方SDK)將現有動態庫轉成靜態庫"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲依賴了第三方SDK,我們是不包含源碼的,所以這部分需要推進社區提供靜態庫的版本,或者通過cocoapods等工具打包SDK的靜態庫版本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)將我們自己的SDK編譯成靜態庫"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於我們自己的SDK,因爲有源碼,所以直接修改"},{"type":"codeinline","content":[{"type":"text","text":"MACH_O_TYPE"}]},{"type":"text","text":" 爲"},{"type":"codeinline","content":[{"type":"text","text":"Static Library"}]},{"type":"text","text":" 重新打包即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)App最低支持系統版本升級到12.2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲iOS在12.2版本及以上才內置了Swift的支持,所以在此之前Swift的動態庫都是隨着 App下發的,也在xxx.app\/Frameworks 裏。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,這個決策是會直接應用到用戶和訂單的,所以是要有數據支持的,我們是根據用戶佔比到達某個閾值才支持12.2的。如果允許,甚至可以升級到iOS 13,因爲iOS13以上dlyd3做了很多加載和緩存的優化。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.2 刪除無用代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果符號越多,很顯然Rebase和Bind的處理時間就會越長,Objc的初始化也受影響,所以我們需要儘可能減少代碼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)通過逆向二進制或者生成linkmap,解析所有方法(TEXT.text)和引用到的方法(__DATA _objcselrefs),找出無用方法刪除"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)解析所有類(DATA.objcclasslist)和引用到的類(DATA.objcclassrefs),找出無用的類刪除"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)使用第三方工具或者clang掃描重複代碼,精簡去重"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)使用"},{"type":"codeinline","content":[{"type":"text","text":"LLVM_LTO"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"GCC_OPTIMIZATION_LEVEL"}]},{"type":"text","text":"等其他編譯選項優化二進制大小"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.3 合併category"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"合併category,可以減少category加載時的耗時。不過這部分收益不大,並且也會影響編程習慣,所以我們並沒有投入很多時間,不再贅述。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.4 刪除+load"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以前會有很多代碼爲了省事,加到了+load中,這部分很顯然佔用啓動時間,所以儘量要把這其中的代碼轉移,可以放到initialize中懶加載,或者放到啓動任務中併發執行,儘量減少這部分的影響。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Xcode調試時,可以通過正則添加所有+load方法的斷點"},{"type":"codeinline","content":[{"type":"text","text":"br s -r \"\\+\\[.+ load\\]$\""}]},{"type":"text","text":" ,然後使用"},{"type":"codeinline","content":[{"type":"text","text":"br list"}]},{"type":"text","text":"打印出所有+load列表,這樣方便我們定位所有+load。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.5 UIApplication子類優化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了減少UIKit Init的時間,可以對UIApplication的子類初始化工作優化。我們這部分不存在,所以沒有做什麼工作。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.6 啓動任務併發"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"想象一下,如果"},{"type":"codeinline","content":[{"type":"text","text":"application:didFinishLaunchingWithOptions:"}]},{"type":"text","text":"裏面執行的所有啓動任務不作任何處理,那麼代碼框架將會很亂,你的優化也只能單點單點去做。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們將"},{"type":"codeinline","content":[{"type":"text","text":"application:didFinishLaunchingWithOptions:"}]},{"type":"text","text":"階段所有方法任務化,一個任務做一種類型的事。任務拆分好之後,就可以根據任務之間的相關性,選擇哪些任務是可以併發執行,哪些任務是必須有依賴關係前後執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以前:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/5c\/5cd64985aa766e62ec4baf9a031bc038.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ef\/efefd0878a9060f7d46f99b6ac098584.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當然,任務的拆分顆粒度也很重要,拆分太粗的話,很難達到最優的組合,可能一個任務裏的方法之間仍然有並行的空間。拆分太細的話,也有可能導致同一時間併發數太多,造成額外的線程切換開銷。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.7 I\/O處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"尤其要注意啓動階段的I\/O,一般出現於讀取磁盤中的文件,比如配置文件等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 Instrument→App Launch 去查看啓動過程就會發現,如果主線程執行出現很多灰色的塊,那就是I\/O,找到這些I\/O產生的方法,儘量在子線程併發執行,避免阻塞主線程。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.8 首頁數據的預加載和懶加載"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首頁上有很多數據要加載,比如圖片、上次緩存在本地的數據等等,這些數據的加載如果在寫代碼時不作特殊處理,那會在主線程執行,不知不覺就會有很多耗時。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)預加載"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對首頁渲染必須的數據,比如一個icon,或者一個翻譯的數據,我們通過在啓動任務(之前提到的拆分的併發任務)中新增加一個預加載啓動任務,專門負責在"},{"type":"codeinline","content":[{"type":"text","text":"application:didFinishLaunchingWithOptions:"}]},{"type":"text","text":" 的過程中併發執行數據的獲取。因爲獲取數據大多比較耗時,所以放在子線程充分利用啓動階段的空閒。同時這類任務大多數是I\/O操作,並不會佔用太多CPU資源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"更進一步,其實可以對首頁用到的資源在運行時作個標記,記錄到磁盤,下次啓動的時候讀取這個記錄,對用到的資源進行提前預加載,這樣避免hard code很多資源名在代碼中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)懶加載"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首頁的數據往往很多,但並不是一開始要全部用到。可以對數據作區分,和第一屏展示無關的,使用懶加載,真正用到的時候再去加載。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.9 二進制重排"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)"},{"type":"codeinline","content":[{"type":"text","text":"page fault"}]},{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於虛擬內存的機制,應用啓動時不會把所有數據加載到內存,而是以頁爲單位逐步從磁盤中加載,內存中的虛擬地址和磁盤中的物理地址有個映射關係。當程序執行時,如果發現要訪問的東西不在內存裏,就會觸發一次"},{"type":"codeinline","content":[{"type":"text","text":"page fault"}]},{"type":"text","text":" ,去磁盤中加載新的一頁。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"啓動階段有很多方法要調用,而這些方法在Mach-O中的位置又是在編譯時確認的。如果有10個方法剛好在不同頁,可能就要產生10次"},{"type":"codeinline","content":[{"type":"text","text":"page fault"}]},{"type":"text","text":" 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"二進制重排要做的就是將啓動階段要用到的方法,在編譯時提前確定,通過.order文件告訴編譯器,這樣這些方法會排布在Mach-O的最前面,之前的10次"},{"type":"codeinline","content":[{"type":"text","text":"page fault"}]},{"type":"text","text":" 很可能就變成一兩次"},{"type":"codeinline","content":[{"type":"text","text":"page fault"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過在Other C Flags中添加"},{"type":"codeinline","content":[{"type":"text","text":"-fsanitize-coverage=func,trace-pc-guard"}]},{"type":"text","text":" 再通過"},{"type":"codeinline","content":[{"type":"text","text":"__sanitizer_cov_trace_pc_guard"}]},{"type":"text","text":"記錄啓動階段所有方法的調用,再將這些寫入到.order文件中,在Xcode的"},{"type":"codeinline","content":[{"type":"text","text":"ORDER_FILE"}]},{"type":"text","text":" 設置中配置即可生效。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過測試,我們的二進制重排大概優化100-200ms。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.2.10 其他通用手段"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對啓動任務和首頁渲染階段,通用的手段是通過instrument,profile出耗時長的任務,對任務針對性地做方法優化。如果有的方法是第三方庫的,那就需要推進社區去更新。我們在做的過程中給Firebase和Google的一些SDK提了很多issue,對方開發人員配合很積極,對我們幫助很大。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、成果如何"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過長期的優化,以上手段全部用完之後,我們的啓動時間從原來的2秒,優化到1秒以內。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在優化啓動時間的過程中,我們的收穫不僅是對啓動時間的優化,也對系統的啓動機制有了更深的瞭解,同時優化了我們自己的代碼,使其變得更加更加健壯和高性能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者簡介"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Shanks,攜程移動開發專家,關注移動端基礎技術。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文轉載自:攜程技術中心(ID:ctriptech)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/smWjs2X8HWvcvKW_DSXYJA","title":"xxx","type":null},"content":[{"type":"text","text":"乾貨 | Trip.com APP 啓動優化實踐"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章