互聯網領域裏有個八秒定律,若是網頁打開時間超過8秒,便會有超過70%的用戶放棄等待,對Android APP而言,要求更加嚴格,若是系統無響應時間超過5秒,便會出現ANR,APP可能會被強制關閉,所以,啓動時間做爲一個重要的性能指標,關係着用戶的第一體驗。java
愛奇藝安卓APP很是重視啓動速度的優化,本文將從啓動過程,啓動時間測量,啓動優化,以及後續監控等方面分享咱們在啓動優化方面積累的經驗。android
要準確的測量APP的啓動時間,首先咱們要了解APP整個啓動過程。 啓動過程,通常能夠分爲如下三類:shell
從上圖能夠看出,啓動過程當中,Cold的模式下,生命週期中作的事情最多,啓動的時間最長,所以,咱們以冷啓動來衡量APP啓動時間。啓動過程當中,如何判斷哪些生命週期影響啓動速度呢?性能優化
咱們知道,APP的啓動和運行,就是Linux系統建立進程和組件對象,並在UI線程中處理組件消息的過程。網絡
啓動過程圖:多線程
App的啓動過程,能夠劃分爲三個階段:併發
當APP啓動時,若是當前app的進程不存在,便會建立新的進程;App主進程啓動後,若是啓動某個組件,而且該組件設置了android:process屬性,組件所運行的進程不存在,也會建立新的進程。app
須要注意的是,若是在啓動階段,初始化的組件中,包含了多個進程,便會建立屢次進程,BindApplication操做也會重複執行屢次異步
進程建立後,會經過反射,執行ActivityThread入口函數,建立Handler,並在當前線程中prepareMainLooper,並在Handler中接收組件的消息,咱們來看一下Handler中處理的消息:函數
sMainThreadHandler中,處理的消息不少,這裏只羅列了,可能在啓動階段可能會執行的操做, 這些操做都是運行在Main Thread中,對啓動而言,屬於阻塞性的。
Activity生命週期,天然須要在啓動階段執行,但,對於Service的建立,Trim_memory回調,廣播接收等操做,就須要重點考慮,其操做耗時性。
前兩個過程,建立進程和UI線程及Handler,都是由系統決定的,對APP開發者而言,並不能控制其執行時間,在本階段,執行BindApplication,和Acitivity生命週期,都是能夠由開發者自定義。
Activity執行到onResume以後,會執行至ViewRootImpl,執行兩次performTraversals,第二次traversal操做中,會執行performDraw操做,同時通知RenderThread線程執行繪製.
從啓動的三個階段,咱們能夠看出,啓動啓動時間的長短,決定因素在於,主線程中所作事情消耗的時間的多少,因此,咱們的優化工做主要集中在,排查主線程中耗時性的工做,並進行合理的優化。Android手機,系統的資源是有限的,過多的異步線程,會搶佔CPU,致使主線程執行時間片間隔增大。一樣的,內存消耗狀態,GC頻率,也會影響啓動的時間。
經過上述的源碼的解讀,咱們已經瞭解了啓動過程,以及可能引發啓動過慢的緣由。接下來介紹一些經常使用的分析手段及時間測量方法。
I 啓動分析工具,主要使用SysTrace,具體的使用方法,請參考官網文檔developer.android.com/studio/comm…。
其中10ms之內的,較短期的Sleeping狀態,不用關注,多是因爲CPU調度的時間片分配間隔引發的;較長時間的Block I/O和Sleep狀態,能夠肯定有阻塞啓動的邏輯在這個階段運行,須要進一步對代碼進行分析定位。
查看CPU佔用狀態:
線程執行:
經過該階段密集程度,反映出CPU佔用率,也能在必定程度上反映出該階段執行時間被阻塞狀況;線程執行狀況統計,能夠查看線程執行時間排名,對執行時間較長的子線程進行優化。
在SysTrace圖中,UI Thread中包含了bindApplication,activityStart,traversal等操做,RenderThread中包含DrawFrame等操做。這些TAG節點是源碼已經添加的,可參考#3.2中介紹。
I Trace上啓動時間:從bindApplication至第二次traversal完成,可認爲UI第一次繪製完成,啓動完成。選中開始點和結束點,能夠查看過程消耗的時間。
在統計APP啓動時間時,系統爲咱們提供了adb命令,能夠輸出啓動時間
I TotalTime: 表示新應用啓動的耗時,包括新進程的啓動和 Activity 的啓動,但不包括前一個應用 Activity pause 的耗時
系統在繪製完成後,ActivityManagerService會回調該方法,統計時間不如SysTrace準確,可是可以方便咱們經過腳本屢次啓動測量TotalTime,對比版本間啓動時間差別。
經過APP啓動生命週期中,關鍵位置加入時間點記錄,達到測量目的。
錄屏方式收集到的時間,更接近於用戶的真實體感。
爲了讓用戶在進入APP以後,更快更流暢的使用服務,因此會在啓動過程當中,提早對一些基礎庫和組建進行初始化操做,這就意味着系統有限的資源會被搶佔,影響啓動時間。啓動時間的優化,是一個平衡性能和體驗的過程。
經過Systrace工具分析,咱們發現愛奇藝愛奇藝安卓APP啓動過程當中一些問題,接下來,咱們就結合具體的業務實踐,進行啓動問題進行優化。
由#3咱們瞭解到,對於一個app而言,App內組件能夠運行在不一樣的進程之中。舉個例子: 一個APP擁有主進程,插件進程,下載進程三個進程,會在啓動階段建立相應的組件,但只有一個QYApplication繼承自系統Application,建立三次進程,QYApplication中attach(),onCreate()方法都會被執行三次。
每一個進程說須要初始化的內容確定是不同的,因此,爲了防止資源的浪費,咱們須要區分進程,初始化Appcation.
I 成果:對多進程應用而言,經過對初始化內容進行梳理,合理區分初始化,會大幅減小內存和CPU佔用。
子線程處理耗時任務,主線程作的事情越少,越早進入Acitivity繪製階段,界面越早展示。
注意:
I 進一步優化:能夠自建線程池,維持必定線程個數,管理任務隊列。
Android系統資源有限,特別是CPU資源,理論上來講,UI線程執行的任務,也沒法保證一直被調度狀態,當併發的線程數過多,UI線程時間片會更短,從而致使啓動時間被變慢。
下面羅列一些常見,容易形成CPU被搶佔的場景:
I 成果:經過對執行時間較久,執行頻率的業務進行優化,將CPU佔有率維持在合理的程度,會大幅減小啓動時間,減小300ms以上。
部分系統的API使用是阻塞性的,文件很小可能沒法感知,當文件過大,或者使用頻繁時,可能形成阻塞。例如:
I 成果:隨着業務量日積月累,正常的系統API的使用,也可能出現問題,經過排除,可減小50-100ms。
佈局的複雜程度,直接影響繪製的時間。
舉個例子,在啓動過程當中,會有須要大的背景圖,只有第一次安裝時使用,後續屬性設置爲android:visibility="gone",可是,雖然設置了gone屬性,不會顯示,但依舊會被解析。
建議:
I 成果:啓動階段的佈局較簡單,經過優化背景圖片的加載,減小50-100ms。
App啓動中過程當中,常常進行Service初始化操做,因爲Service使用通常不涉及界面,可能會認爲初始化生命週期不在主線程中,其實否則,在3.2的啓動過程源碼介紹中講到,Service的生命週期,也屬於主線程Handler接收的Message之一。
建議:Service生命週期中,注意邏輯執行時間性能優化,初始化儘可能延後。
I 成果:取決於初始化Service的生命週期執行時間,可減小200ms以上。
對於APP首頁展現不須要的初始化邏輯,可延後至首頁繪製完成後初始化。
注意:
進一步優化:可將業務邏輯的初始化劃分爲,首頁繪製後,5s,10s,20s三個階段分別初始化,防止首頁繪製執行任務過多形成掉幀。
I 成果:釋放繪製階段的CPU,可將複雜的繪製提早200ms以上。
穩定的用戶體驗依賴於持續的監控,愛奇藝爲監控啓動性能創建了一套監控體系,測試,工具,開發等幾個團隊從不一樣的緯度搭建不一樣的監控方案
SysTrace經過TAG節點能夠清晰展示,啓動過程以及方法執行時間,可是,從發現問題,而後經過節點去定位問題,是一件很繁瑣的工做,若是大家工程編譯又比較慢,簡直讓人崩潰。
在Android工程編譯的過程當中,指定class,在方法先後,自動化插入Trace節點,統計方法執行時間。
流程:
經過工具的操做,可以作到不用修改原有工程文件,自動在打包時注入TAG節點和邏輯代碼,配置文件能夠循環利用,提升分析效率,節能環保。
啓動時間,因爲不一樣的機型性能同,Android系統版本不一樣,同一APP版本啓動時間,相差很大,因此統計通常以同一手機,不一樣版本作比較,儘可能保證手機狀態一致。
SysTrace手機優化時間對比:
腳本屢次啓動時間收集對比:
通過多個版本的持續優化,有無廣告兩種不一樣的場景下,啓動時間分別減小40%和35%,啓動速度獲得了較大的提高。
啓動時間的優化和監控,是一項長期的任務,須要對異常的狀況進行分析,對可能形成阻塞的代碼邏輯進行合理的優化,很是感謝各個業務團隊支持和配合。