啟動性能是 App 使用體驗的門面,啟動過程耗時較長很可能導致用戶使用 APP 的興趣驟減,抖音通過對啟動性能做劣化的 AB 實驗也驗證了其對于業務指標有影響顯著。抖音擁有數億的用戶,啟動耗時幾百毫秒的增長就可能帶來成千上萬用戶的留存縮減,因此,啟動性能的優化成為了抖音 Android 基礎技術團隊在體驗優化方向上的重中之重。
本文基于過往對抖音 Android 客戶端做啟動性能優化的實戰經驗總結提煉出普適性的方法論,并將該過程中沉淀的工具加以分享,希望能給大家帶來一些新的思考。
抖音 Android 性能優化系列往期文章回顧:抖音Android性能優化:新一代全能型性能分析工具Rhea
帶著問題出發

假如你要負責優化抖音的啟動性能,你會怎樣去規劃整體的優化方案?你可能會一下子想到很多方面的細節點,比如:要優化主線程耗時、要減少布局層級、要對某些啟動任務做按需加載或預加載、要避免主線程 IO、要對線程使用進行優化、還要有分析工具幫助定位性能問題等……
然而,該如何系統性地把這些細碎點組織起來并按照一定的章法來落地啟動優化呢?此時,需要我們在具體細節點之上有進一步的問題分解與深入思考,最終形成一套完整的方法論,不僅能覆蓋所有細節點,還能切實指導在實戰中達成啟動優化的效果。切實有效的方法論必然是從實戰中經過千錘百煉才能形成的,而抖音龐大的用戶基數又進一步保障了方法論的可行性與普適性。那么接下來讓我們帶著前述問題來看抖音的啟動優化方法論是怎樣的又是如何應用于實戰之中的。
啟動優化方法論
抖音的啟動性能優化方法論分為五部分,分別是:理論分析、現狀分析、啟動性能優化、線上驗證與防劣化。

這五部分間存在明顯的先后順序,又能閉環達成可持續的啟動性能優化,下面將對這五部分做詳細闡述:
理論分析
理論分析放在最先是為了從一開始就避免讓視野受到限制,很多同學往往一開始接手啟動優化就容易陷入對各種現狀細節的分析,拘泥于片面的潛在可優化點,這樣就難以做到對全局和優先級的把控,所以,我們應該首先跳出現狀,從更加全局的視角來思考整體優化的目標和策略。這里可以利用特斯拉創始人——埃隆·馬斯克所推崇的“第一性原理”思考法:
“通過第一性原理,把事情升華到最根本的真理,然后從最核心處開始推理。”
基于此,我們在做啟動優化的理論分析時可以從更本源的角度出發做到全局思考,比如抖音會做從進程創建到頁面展示的全啟動路徑分階段耗時分析、還會按照消耗的系統資源類型做耗時成因分析,通過這種極致的耗時分析可以帶來極致的優化策略,此外,從全路徑出發還能夠發現容易忽視的問題、探索優化的極限。
現狀分析
在完成理論分析后,我們基本具備了全局的視角,并且也大致清楚了整體的優化目標和策略,接下來就要基于此來做現狀分析從而明晰實現目標的具體路徑:
- 首先使用 profile 工具對可優化點進行摸底:其實不合理的高耗時點就是潛在的優化點,并能按照前述的理論分析歸入一個或多個耗時成因中;
- 然后結合線上的指標數據確定最終優化方向:線下摸底的潛在優化點要結合其線上打點確認是否為普遍耗時,再根據耗時成因明確大致的優化思路、實施成本和預估收益。
在這部分需要尤其注意三點:優質的 profile 工具(這里推薦使用同樣來自基礎技術團隊的“btrace 開源!基于 Systrace 高性能 Trace 工具”)、線下 trace 結合線上監控綜合分析、根據投入產出比評估實施優先級,這三點是保障切實有效取得啟動優化收益的關鍵。
啟動優化
在完成了理論和現狀分析后,就可以根據規劃的路徑來實施具體的啟動優化項了。在實施過程中,主要考慮主線程優化、后臺線程優化和全局優化三個維度:
- 主線程耗時優化需要在啟動全路徑各階段中細化具體的耗時成因,如:CPU Time、CPU Schedule、IO wait、Lock wait 等,完成耗時歸因后可以使用逐步升級的優化策略來逐個擊破:對于首屏所必須的耗時邏輯做正面優化(可使用縮減耗時邏輯、異步并發、延遲加載等手段)、對于非首屏必須的耗時邏輯做按需加載(需要架構優化的基礎)、對于優化后仍存在耗時的邏輯嘗試做業務降級(大都有損需評估全局收益);
- 后臺線程優化策略與主線程類似,在此基礎上還可以實施后臺任務縮減、線程收斂、開啟多進程等優化措施;此外,主線程和后臺線程均存在較多啟動任務且彼此間可能存在關聯,因此,可以對全局的啟動任務做依賴關系梳理并實施精細化的任務重排,旨在減少依賴任務間的等待耗時;
- 全局優化主要是指業務無關的通用的全局優化策略,如虛擬機層面或 IO 層面的優化等。
線上驗證
在完成了具體的優化項施工后,就來到了線上驗證大盤收益的階段。這個階段有三點需要注意:
- 線下的優化一定要有線上的指標反饋,線下的優化項因為設備或操作習慣差異往往難以評估是否具備普遍影響,只有當相應的線上指標取得正面反饋后才能驗證拿到了有效的優化收益;
- 線上指標需要結合均值與分位值綜合來評估,只關注啟動耗時的均值往往會掩蓋低分位設備的現狀,這部分設備可能占比不高,對均值影響有限,但抖音龐大的用戶基數乘以該比例仍舊是不小的數量,為了保障該部分用戶的啟動性能體驗,抖音一般會分 50%、70%、90%三個分位值來評估指標;
- 在驗證收益時通過 AB 實驗達成,這樣做不僅能控制變量確保優化項的嚴格有效,還能借此來觀察性能優化所帶來的業務指標收益,這些都可以作為規劃后續啟動優化方向的參考指導。
防劣化
在線上驗證優化措施取得切實收益后,并不是萬事大吉了,持續保持住優化效果才算完整達成了啟動性能優化的目的。其實不僅是啟動優化,整個性能優化領域都是圍繞著“攻”和“守”來展開的,“攻”即為前述的分析與優化,而“守”則是防止劣化,在防劣化方面大家往往不會像優化的方面那么重視,但實際上能防止劣化是可持續取得優化效果的前提(否則新的優化效果會用于彌補劣化甚至入不敷出),并且防劣化相比于優化是更能持久有益的。
抖音啟動性能防劣化的進程分為了三個時期,不同時期有不同的表現與應對手段,這很可能是大多數 APP 優化啟動性能都要經歷的,這里提煉出來以供參考:
- 快速下降期:此時一般位于啟動優化的初始階段,優化空間很大,伴隨有小幅度的劣化但往往都能被更大幅度的優化抵消且還仍有收益,這時應該抓大放小,按照更高投入產出比的策略重點推進優化,同時也抽出少部分精力治理修復成本低的劣化。
- 瓶頸期:到了該時期絕大部分優化收益已經拿到,想進一步做到優化往往需要投入更多成本,且優化幅度有限,整體的投入產出比不高,同期還會伴隨有中小幅的劣化,此時需要建立完善的線上線下監控體系,及時發現并修復劣化,此外還要通過架構改造從源頭上限制劣化的發生,綜合保障優化的收益不會被劣化抵消。
- 劣化期:這個時期往往出現在年關或重要節日期間,這類時間點往往有重要且緊急的活動項目上線,眾多關聯方面均要為其開綠燈,啟動性能指標也不例外,為了保障活動效果可能要加入若干耗時的主線程啟動任務,所帶來的的劣化幅度往往比較大,此時需要對齊預期并在活動結束后及時修復。
啟動優化方法論的應用實踐
古人云“紙上得來終覺淺,絕知此事要躬行”,前述的方法論講得再詳細再透徹也會與實際的落地存在隔閡,為了做到真正的學以致用,下文將細致講解如何將啟動優化方法論應用于實踐之中。
理論分析的實踐
抖音在理論分析部分會對啟動流程分別作全路徑分析和耗時成因分析,前者用于發現全路徑各個階段的潛在耗時點避免疏漏,后者用于系統性地將各個耗時點歸因從而引導我們找尋優化思路,關于這兩部分的具體實踐如下:
啟動性能全路徑分析:抖音的啟動路徑和大多數 APP 類似,整體分為兩大階段和兩個間隙,它們按時間順序排布為:Application 階段、handle message 間隙、Activity 階段和數據加載間隙,全路徑各部分細分涵蓋的內容如下圖所示:

APP 進程由 zygote 進程 fork 出來后會執行 ActivityThread 的 main 方法,該方法最終觸發執行bindApplication,這也是 Application 階段的起點;然后是我們在應用中能觸達到的attachBaseContext階段,4.x 的機型在該階段具有較長的 MultiDex 耗時可以做針對性優化(可參考“開源 | BoostMultiDex:挽救Android Dalvik機型APP升級安裝體驗”),本階段也是最早的預加載時機;接下來是installProvider階段,很多三方 sdk 借助該時機來做初始化操作,很可能導致啟動耗時的不可控情形,需要按具體 case 優化;此后就到了 Application 的onCreate階段,這里有很多三方庫和業務的初始化操作,是通過異步、按需、預加載等手段做優化的主要時機,它也是 Application 階段的末尾。

在Application 階段和 Activity 階段之間往往會不可避免地被插入很多 post 到主線程的消息及相應待執行任務,這是拉長啟動耗時的另一不可控問題點,需要加以監控治理或通過消息調度優化來盡量減小此間隙。

在來到 Activity 階段后,首先經歷的是其onCreate生命周期,這里涵蓋了首屏業務優化的主要場景也是開啟異步并發的主要時機,在其中有個重要的 setContentView 方法會觸發 DecorView 的 install,可嘗試對 DecorView 的構建進行預加載;后續自然來到View 構建的階段,該階段在抖音上相當耗時,可采用異步 Inflate 配合 X2C(編譯期將 xml 布局轉代碼)并提升相應異步線程優先級的方法綜合優化;再來到View 的整體渲染階段,涵蓋 measure、layout、draw 三部分,這里可嘗試從層級、布局、渲染上取得優化收益。

最后是首屏數據加載階段,這部分涵蓋非常多數據相關的操作,也需要綜合性優化,可嘗試預加載、緩存或網絡優先級調度等手段。

此外,針對全路徑所有階段還可以實施通用性的優化項,如:啟動任務調度框架、類重排、IO 預加載、全局通用性框架優化等。

啟動耗時成因分析:所有的耗時均因代碼運行時不合理地消耗系統資源產生,而不合理的耗時點正是需要做歸因分析之處。抖音按照不合理耗時點消耗的主要系統資源類型劃分出五大成因,分別是:CPU Time、CPU Schedule、IO Wait、Lock Wait 和 IPC,下面分別對各成因進行剖析:
- CPU Time 指占用 CPU 進行計算所花費的時間絕對值,中斷、掛起、休眠等行為是不會增加 CPU Time 的,所以因 CPU Time 開銷占比高導致的不合理耗時點往往是邏輯本身復雜冗長需要消耗較多 cpu 時間片才能處理完。比較常見的高 CPU 占用是循環,比如抖音啟動時遇到過一個 so 加載耗時,最后定位原因是在解壓 so 的時候,遍歷 ZipEntry 的次數過多導致,一個可行的優化策略就是可以把 so 所在的 ZipEntry 提前,遍歷完 so 的 ZipEntry 之后可以提前中止遍歷,而不需要遍歷剩下的無效 ZipEntry。除循環之外,反射也是導致 CPU Time 的重要原因,像在序列化/反序列化、View Inflate 時,都有大量的反射操作,反射的耗時主要是字符串去查找 Method 或者 Field,這個優化策略也可以考慮提前查找 Method 和 Field 緩存起來,或者是通過內聯來降低 Field 數量等。另外一個常見的 CPU 耗時是類加載,類的加載過程包括:Load,從 Dex 文件里讀取類的信息,可通過類重排優化;Verify,驗證指令是否合法等,通過關掉 Class Verify 可以優化該過程,同時高版本的 vdex 也是為了優化 verify 過程而設計,在 dex2oat 的時候做 verify,verify 之后的結果保存成 vdex,后續只需要加載 vdex;Link,給 Field、Method 分配內存,按照名字排序以方便后續反射的時候查找 Field、Method 等,這個過程的優化,art 虛擬機采用了 ImageSpace 的方案進行了優化,將 Link 后的內存保存為 image 文件,后續可以直接 load 這個 image 文件,省去了 Link 過程;Init,類的初始化。
- CPU Schedule 在分析時主要針對主線程,是指主線程處于可執行狀態但獲取不到 cpu 時間片,這類耗時可能和線程調度等有關,最終導致分配給主線程的 cpu 時間片不足以及時處理完其內任務。由于主線程的線程優先級比其他線程的優先級要高很多,通常影響并不大,事實上抖音做了線上用戶的啟動耗時統計,這部分的耗時占比也是不大的。不過有一個場景需要關注,就是渲染,渲染是需要 RenderThread 提交 GPU 的渲染命令,而 RenderThread 并沒有主線程那么高的優先級,因此比較容易受 CPU 的負載的影響,導致渲染耗時,這個對于啟動來說影響并不算大,啟動只有一次首頁的渲染,占整體時間的比例不算大,但對于流暢度的影響就會比較大。這類耗時的優化主要還是從降低 CPU 的負載的角度考慮,比如業務降級、業務打散等手段。抖音還通過對 RenderThread 優先級的提升優化,拿到了不錯的收益。
- IO Wait 指發生了 IO 操作需要等待 IO 返回結果,這類耗時可能發生在讀取資源和文件,類加載,甚至在內存不足時的 PageFault 都會導致 IO Wait。Resources 的相關的操作耗時,主要是需要從 apk 里讀取資源文件,優化策略可以有預加載、資源重排、資源異步加載等。類加載的 IO Wait 和 Resources 類似,也可以通過類的重排、預加載等優化方案。文件讀寫導致的 IO Wait 又分為業務文件和系統文件,業務文件指業務邏輯的讀寫文件,一般都可以通過異步來解決,而系統文件的例子是 dex 的讀寫,抖音的 IO Wait 很大一塊是它貢獻的,目前的思路還是做 dex 的重排和 IO 的預讀來嘗試優化。
- Lock Wait 也是主要針對主線程,指其處于等鎖狀態,等待被其他線程喚醒或自己超時喚醒,導致這類耗時的問題種類多樣,大體也是可以分為業務鎖和系統鎖,業務鎖主要是被主線程等待的業務邏輯未能及時處理完,優化思路一般是移除主線程的鎖等待邏輯或者加快被等待的業務邏輯的執行速度。系統鎖主要有:String InternTable Lock,ClassLinker Lock,GC Wait Lock 等,目前抖音正在嘗試優化這幾類的鎖耗時。
- IPC 指進程間通信,操作系統大都含有相應的機制,Android 中所特有的 IPC 機制是 Binder,由于進行 IPC 調用往往需要等待通信結果本質上這也算是一種 Lock Wait,但 Android 特有 Binder 機制所以單獨列出,這類耗時可采用減少或替代 Binder 調用等手段來優化。
綜合前述的五大耗時成因,這里舉一個分析啟動階段 UI 耗時成因的例子作為實踐參考,根據 UI 界面的生命周期(一般劃分)——UI 構建、數據綁定、View 顯示三個階段分別進行分析:

- 在UI 構建階段中首先要對界面布局的 xml 文件進行解析,這會導致 IO Wait 耗時,在接下來要解析 xml 文件中的 TagName 從而獲取對應 View 的 class 會用到反射、創建各子 View 實例并生成 View 樹又會用到循環遞歸,兩部分都會增加 CPU Time 的開銷。
- 然后是數據綁定階段,該階段主要分兩部分,一部分是對數據做請求、解析、適配,另一是部分是將適配好的數據填充進 UI 中,前一部分往往會涉及到 Json 解析成 Data Class 實例,這里就可能涉及反射、循環遍歷嵌套的數據類結構等增加 CPU Time 的操作。
- 最后是View 顯示階段,常見的 measure、layout、draw 三大渲染 View 的步驟就在其中,它們同樣會產生遞歸遍歷父子 View 的耗時,此外這里還涉及將應用層計算好的渲染 View 的數據傳遞給系統層做最終的像素點排布,那么必然又會產生 IPC 耗時。
從這個例子可見即使再復雜的場景只要我們進行細粒度的分析,都能將耗時點歸入前述某一成因中。
現狀分析的實踐
如前文方法論所述,現狀分析包括線下 Profile 數據與線上監控數據的對照分析,綜合這兩部分可以明確切實影響大盤啟動性能的普遍耗時點,從而確保要做的優化項是行之有效的。下面分別講述這兩部分數據的分析實踐:
線下 Profile 數據分析:Profile 主要是指使用性能探測工具抓取應用啟動路徑各階段的耗時和系統資源消耗情況,常見的開源 Profile 工具有 TraceView、Systrace、Android Profiler 等,這些工具各有優勢但均不能完全滿足抖音做線下 Profile 的需求(詳見后文“啟動性能優化工具”部分的講解),為此,抖音自研了“新一代全能型性能分析工具 RheaTrace”滿足了需求。通過該工具我們可以在線下抓取整個啟動路徑的 Trace 文件,其整體樣式與 Systrace 一致,但是涵蓋了更多的信息點,一個樣例 Trace 文件如下圖所示:

這里需要注意抓取 Trace 一定要基于 release 包,debug 包中往往涵蓋諸多調試邏輯可能影響啟動性能,導致 profile 數據與實際使用情形存在偏差。在查看 profile 數據時,首要觀察主線程,尋找其中不符合預期的耗時方法,抖音將主線程耗時在 5ms 以上的方法均認定為不符合預期;然后在所有不符合預期的方法中尋找 Top n 的耗時點,逐個分析耗時原因、尋找突破口;耗時原因需要結合方法實現邏輯以及諸多運行時信息綜合分析(這里可以參考 google 官方文檔“瀏覽 Systrace 報告”),需要關注的運行時信息有方法執行時段對應的 CPU 負載、線程狀態的顏色標識值、鎖信息、IO 耗時、Binder 調用耗時等,根據這些信息判定引起方法耗時的主要原因,再結合理論分析中不同階段、不同系統資源類型探尋優化手段。
線上監控數據分析:這部分數據的分析主要是用作參照和補充,參照是指線下 Profile 數據分析出的耗時點要對照線上數據確認其在大盤中存在普遍耗時,補充是指線下 Profile 數據未能復現的耗時點可能存在于線上大盤中,這部分漏掉的耗時點需要在線下嘗試復現、歸因后實施優化。這里有個很重要的點是:該如何對線上的啟動性能指標做監控,這是保障線上數據能真實反映用戶體驗并且與 QA(做競品測試等)和業務方(判斷業務需求是否影響啟動性能等)達成一致的前提,下面將對這部分做詳細闡述,分為啟動性能指標的定義、統計和校準三部分:
- 啟動性能指標定義:啟動指標定義主要在于如何確定啟動路徑的起點與終點。起點的備選項有下圖中的三個點以及 Application 的 attachBaseContext 方法:
- 下圖中“點擊圖標”后的/proc/self/stats starttime 是內核中記錄的 App 啟動時間點,該數據的 Android 版本兼容性良好也比較貼合真實情況,可以作為備選;
- 接下來的 Process.getStartElapsedRealTime 是 Framework 中記錄的 APP 進程創建起點,該 API 是 Android N 起才提供的兼容性較差;
- 再往后是 Application 的構造函數,按照 Android 官方生命周期式的開發模式通常不會往 Application 的構造函數中加邏輯,所以不建議在這里記錄起點;
- 最后是大家熟悉的 attachBaseContext 方法也是 Application 生命周期中非常早的一個點,可以在這里記錄啟動點,雖然可能和真實情況有小幅差距,但能夠起到基本的對照效應并且處于 APP 邏輯可以干預的范圍內,抖音的啟動路徑起點選定的正是此處,此外也可以結合前述的內核中啟動時刻綜合觀測。

- 而關于終點的定義同樣有幾個備選項:Activity-onResume、Activity-onWindowFocusChanged、View-dispatchDraw 和 DecorView-post:
- 少數 APP 在選定冷啟階段終點時可能會選擇首頁 Activity 的 onResume 時機,但此時整個 Activity 還不是完全可見,與用戶感受到的冷啟階段結束時刻有一定的差距;
- 基于上述原因,更多 APP 會選擇首頁 Activity 的 onWindowFocusChanged 時機,抖音也是選擇的此時機作為冷啟過程的終點,此時首頁 Activity 已經可見但其內部的 view 還不可見,對于用戶側已經可以看見首頁背景,即認為冷啟階段到此結束,后續的首頁內 View 繪制歸入首刷過程中;
- dispatchDraw 從 View 可見這個角度講應該是比較接近用戶感受的,但其受業務改動影響較大,不利于把控冷啟時間及維護;
- 最后是通過 DecorView 在 attachToWindow 前 post 一個 runnable 來打點的方式,該方式可以保障在業務 View 完成渲染后做打點,但該方式可能會受業務同學做懶加載在打點前插入邏輯的影響,因此抖音的冷啟終點也未選用該時機。
- 啟動性能指標統計:在統計性能指標時有個關鍵點往往被大家忽略,就是分位值的概念,由于平均值相對更通俗易懂且對大盤突發問題敏感往往作為首要統計指標被關注,但其存在波動較大不利于大盤監控以及難以體現不同分位機型啟動性能差異的問題,而分位值更有利于全面監控且各分位波動相對較小,此外對于低端機的性能問題能夠更好地顯現出來,有助于做專項優化,在優化抖音的啟動性能時我們會重點關注 50 分位和 90 分的性能指標,不過分位值也存在一些缺點,比如:概念理解起來相對復雜、個別 bad case 分散到各分位不容易體現出來等,因此比較好的實踐是:日常優化主要統計分位值,平均值作為輔助完善監控體系。
- 啟動性能指標校準:由于啟動路徑往往比較復雜,因此添加了啟動性能埋點后還需要額外的校準,總的原則是需要保障指標數據能切實反映大盤用戶情形。在添加客戶端埋點時最好是先梳理再分主路徑和重點 case 分別打點,此外還要對若干異常 case 的數據進行剔除或分類避免污染打點數據,比如抖音在添加啟動時間打點時就會對開屏廣告、?;钸M程、push 拉起、deeplink 拉起、啟動期間退后臺、新用戶啟動等場景進行過濾或分開統計。
啟動優化的實踐
在做完理論分析與現狀分析后,我們基本對全局待優化點及其大致優化方向會產生整體的認知,在開始落地各個優化措施之前還有很重要但往往會被忽略的一步——按優先級排布優化項、制定整體優化方案,這一步在很大程度上制約著后續啟動優化的收益預期與進展把控,這兩點對于按時達成啟動優化的終極目標都至關重要。前述中提及了對“優先級”的把控,這點是制定整體優化方案的重中之重。
從抖音啟動優化實踐總結來看比較好的優先級策略是按照“投入產出比”來排布優化項,顧名思義:投入人力越少但優化幅度越大的優化項越應該排在前期,因為所有的性能優化歷程都勢必會經歷從高收益到低收益的變化,那么相應的在排布優化項的前后順序時也需順應此規律,最終呈現的態勢即為:前期以小成本快速降低大盤啟動耗時,后期逐步提高投入突破各個瓶頸型耗時點(更后期大規模重構僅能減少幾十毫秒啟動時間的情形也應在預期之內),全過程同期加強防劣化機制,最終做到可持續優化。

在完成前述的全局優先級排布及方案制定后,才算真正來到了實施優化的階段,在這個階段所要用到的各類優化策略及配合方法在前文方法論部分已有詳細講述,在實戰部分首先要補充一下前述幾類優化策略按照“性能無損”、“業務無損”的區別劃分,整體如上圖所示,此外,我們會結合抖音啟動優化實戰經驗列舉各優化策略下可實施的優化項,以供參考:
- 正面優化:刪減非必要的啟動邏輯、開屏頁與首頁 Activity 合并、獲取進程名從 IPC 轉反射方式等;
- 按需優化:ContentProvider 中過早初始化邏輯轉為使用時初始化、多進程由啟動時加載轉為使用時或特定場景觸發加載等;
- 延遲優化:4.x 機型延遲執行 Multidex.install 中的 Odex 操作、主線程消息隊列中非啟動必要消息延遲執行、啟動路徑非高優業務邏輯延遲初始化等;
- 運行時優化:CPU 提頻、語言層面優化(內聯、替換反射、避免用 Kotlin 的 Range 循環)、關閉 Verify Class、4.x 機型抑制 GC、主動觸發 AOT 編譯、資源重排、類重排、dex 重排等;
- 異步優化:異步預加載(ShardPreference、實例化對象)、異步 inflate view、線程收斂等;
- 降級優化:極速版、組件化降級、非必要耗時邏輯按人群/地區降級等;
- 綜合優化:啟動任務調度框架、啟動路徑重構、前后臺啟動任務精細化重排、后臺負載優化等,這些優化項屬于前述優化思想的綜合應用,一般不局限于單方面的優化。
通過上述列舉的各策略優化項你可能會發現,這其中有的優化項其實會對個別業務性能或功能有損,但最終對于啟動性能是有顯著提升的,那么此時需要按照“全局收益最大”的策略來綜合評估這些優化項的可落地性,并不是只看單點的得失,這種全局性的思維在性能優化中非常重要。
線上驗證的實踐
這部分在前述的方法論中已針對三個關鍵點闡述得比較細致,這里僅針對三個關鍵點在落地時的技巧或注意事項加以補充:
- 線下的優化一定要有線上的指標反饋:由于線上設備的固有硬件性能各異,所以需要有足夠量級的用戶啟動打點數據才能相對準確地判定線下的優化是否在線上產生了效果,這個量級從抖音啟動優化中摸索的經驗來看一般達到 100 萬即可;此外,觀測啟動性能數據的時間點也需要把控好,這是由于每次發布升級版 APP 后,大都是性能相對好的手機會先升級,這個現象會等導致發版初期的啟動性能數據整體偏好,不能反映真實大盤情形,因此,抖音一般會選取每個版本發版后 4-5 天(可能隨 APP 升級覆蓋安裝的速度不同而不同)的數據判定大盤情形。
- 線上指標需要結合均值與分位值綜合來評估:在抖音啟動優化實踐中,啟動耗時均值會更多用于大盤情形評估或線上監控中,而作為性能優化的同學最主要關注的是 50 分位機型的數據,這是能代表過半數用戶啟動性能水準的指標,此外 90 分位以上的機型也需要我們額外關注,這類機型非常容易放大啟動性能問題,從實際來看,90 以上相對 50 的絕對分位數差了不到一倍,但冷啟耗時卻可能差到 2 倍左右(如下圖所示抖音在某段時期的各分位冷啟耗時情形),這說明低端機的用戶啟動體驗是明顯可感知的差,基于我們曾經做過的劣化實驗結果來看,這些機型的啟動性能如果不能有效提升,將有很大概率減少其留存。

- 在驗證收益時通過 AB 實驗達成:AB 實驗相對于觀測不同版本的大盤數據來看更具有嚴謹性,因此在產出實驗結論前同樣需要保障數據量和時間跨度,抖音在開啟性能的 AB 實驗后,一般會讓對照組及實驗組進組用戶各達到 100 萬并保持至少 5 天后才進行實驗的數據分析并產出結論,這樣可以基本保障所有相關指標的穩定及置信。
防劣化的實踐
防劣化的體系建設是個比較復雜的工程,要做好是有非常大的挑戰的。抖音從最早的線下手動的分版本測試開始,經過了逐步的摸索優化,演變到當前涵蓋了代碼提交時靜態檢測、線下自動化劣化測試和歸因、灰度劣化發現和歸因、線上常態化的劣化監控和歸因。防劣化是一個漏斗,從代碼提交階段到線下測試階段,再到灰度發布階段,再到線上版本發布階段,我們希望劣化能夠更前置的發現,每個環節都盡可能的發現解決更多的劣化,保證更少的劣化被帶到線上。
防劣化的有幾個難點:一是劣化檢測的準確率和召回率,為了更多更準確的發現劣化;二是劣化的準確歸因,發現劣化之后,如果不能精準的指出劣化的原因,需要投入比較多的人力資源和時間定位劣化原因,影響劣化解決的效率;三是劣化的修復,如果是比較嚴重的劣化,可以采用阻塞發版限期解決的方式,是比較容易推進解決的。但是從抖音的實踐來看,當啟動優化到了深水區之后,優化的速度已經比較緩慢,需要關注幾十毫秒級別的劣化了,假設我們解決了一二兩個難點,發現了這些輕微的劣化,但是如何推進業務去解決這些小劣化也同樣是一個難題。我們需要能夠量化出這些劣化對業務的影響,針對不同的劣化量級,和業務對齊優先級,確定標準的劣化修復流程,才能夠保證劣化不會被帶到線上影響大盤用戶。
防劣化是一個長期的工作,抖音投入已經有一年多了,目前整體效果還不錯,在這個過程中也積累了比較多的經驗,之后會專門寫一個抖音的防劣化系列文章來給大家介紹我們的技術成果。
啟動優化工具
古人云“工欲善其事必先利其器”,在啟動性能優化領域也是一樣,我們不僅需要趁手的工具來定位優化耗時問題,還需要盡量自動化的工具來持續發現劣化問題,也就是說整個啟動優化在“攻”和“守”的兩大方面均需要工具的輔助。那么下面將針對這兩部分的工具分別進行介紹及分享抖音在啟動優化工具方面的探索:
線下分析工具
這部分主要針對業界常見的 APP 性能探測工具進行基本原理解析及優缺點對比,具體包含的工具有:TraceView、CPU Profiler、Systrace,此外還將提及抖音自研的“抖音Android性能優化:新一代全能型性能分析工具Rhea”:
- TraceView:Instrumentation 模式下采用 AddListener 的方式注冊 MethodError、MethodExited、MethodUnwind 的回調來采集方法起止時間;Sampling 模式下使用一個 SamplingThread 定時主權線程堆棧,通過對此的堆棧對比近似確定函數的進入和退出時間;雖然是官方提供的工具,但兩種模式本身都存在比較大的性能損耗,可能帶偏優化方向;
- CPU Profiler:整體通過 JVM Agent 實現,具有完成方法調用棧輸出,且支持 JAVA、C/C++方法的耗時檢測,上手比較簡單,但其同樣存在性能損耗較大的問題,且一般僅用于 debug 包,release 包需要額外添加 debuggable 的配置;
- Systrace:基于 Android 系統層的 Atrace 實現,Atrace 又基于 linux kernal 層的 ftrace 實現,ftrace 在內核中通過函數插樁獲取耗時;其自身性能損耗比較低、數據源豐富且具有較好的可視化頁面,但其默認監控點較少,在 APP 自有代碼中的監控點需要手動加入,比較麻煩;
- RheaTrace:這是抖音基于字節碼插樁結合 Systrace 及 Atrace 自研的工具,其具有自動加入監控點、各類耗時信息全面、性能損耗低等特點,是抖音日常在線下實施性能優化時首選的工具,其細節詳見抖音Android性能優化:新一代全能型性能分析工具Rhea,這里不再贅述。
RheaTrace 目前是抖音性能優化同學的主要工具,它不僅僅是一個工具,也是一個平臺。除了 Systrace 自帶的性能數據之外,我們增加了業務的函數耗時插樁的數據,可以更全面地對耗時進行分析。但是這些數據還不夠,我們支持以插件的形式,增加自己定制的數據,比如為了優化 IO 的耗時,我們通過 hook 增加了更精細化的 IO 的信息,輔助定位 IO 的耗時問題;抖音的類加載耗時也是有些嚴重,我們也 hook 了類加載,增加了類加載的性能數據。我們要極致地優化抖音啟動時間,以上這些數據是不夠的,還有鎖、View 耗時信息等相關數據補充,給性能優化的同學提供全方位的性能分析工具。
除了 RheaTrace 之外,還有一些特定場景的小工具,比如線程分析工具、內存分析工具、高頻函數分析等。由于篇幅有限,就不在這里一一介紹,后面會有專門的系列文章來介紹。
線上監控工具
上面介紹啟動優化方法論的時候我們提到了,不能只是看線下的性能分析,線下的分析結果并不能完全代表線上大盤用戶的情況。我們分析線上的性能數據,一方面能夠驗證我們的線上優化效果,另一方面能夠從線上多個維度的數據里指導后續的優化方向。
線上監控工具和線下的差異點主要在低性能損耗和兼容性,我們將 RheaTrace 做了改造,使其能夠滿足線上的監控要求。性能損耗上,我們將監控的性能損耗控制在 1%以內,包大小控制在 200KB 以內,基本實現了線上全量用戶的啟動耗時監控。通過啟動路徑的全量插樁,可以針對啟動路徑的各個階段進行監控,一是可以發現線上用戶哪些任務比較耗時,可以針對性的優化,讓更多用戶受益;二是可以監控線上的啟動任務,如果發生了耗時增加,那么說明有劣化,這比監控到啟動時間的劣化,要更容易定位到原因。除了線上的全量慢函數監控之外,我們的線上啟動監控還會細化IO、鎖、GC等多種維度的耗時數據,幫助定位線上為什么耗時慢,提供新的優化方向。
總結一下線上啟動監控工具的思路就是:將線下的性能分析數據,低損耗的移植到線上,觀察線上用戶的性能數據,線上線下相結合的分析啟動耗時,為啟動優化提供優化方向指導。
啟動性能優化之路去向何方
看了上文關于啟動性能優化如此多的理論與實踐,想必你已經意識到啟動優化之路注定是不會平凡的,抖音在這條路上探索了 2 年之久且仍未到達盡頭。在這條路上勢必會經歷前期的坦途、中期的迷茫與后期的瓶頸,但無論如何都要一直堅定地走下去,因為只要業務還有一天在迭代那么啟動性能就有一天存在挑戰的可能,所以啟動優化之路的未來必然是無盡頭的。
既然如此,那么我們的重點就應該從何時才能走完這條路轉移到如何走得更精彩之上,甚至到最后能夠做到把控這條路的走向,這或許也能算作另一種意義上的走完啟動優化之路,那么什么才算走得更精彩以及把控路的走向呢?
迷茫時慢下步子再分析全局的耗時點尋找到新的優化策略、遇到瓶頸時先暫時放緩追趕指標嘗試從代碼重構上挖掘深層的收益、不斷開拓跨領域(如端上智能降級)結合的優化方向……這些或許都能稱作是一種精彩,并且會因人而異,最終,當這種精彩累計得足夠多之時我們很可能會發現啟動優化之路上已知的所有岔路口全被走了個遍,同期 APP 的啟動性能也很可能已經達到了再優化也沒什么明顯業務收益的地步,并且出現的任何劣化點都能及時被解決掉,那么這時不出意外的話,啟動優化之路走向的把控權已經盡在你手中了。