愛奇藝Android客戶端啓動優化與分析

1 簡介

互聯網領域裏有個八秒定律,若是網頁打開時間超過8秒,便會有超過70%的用戶放棄等待,對Android APP而言,要求更加嚴格,若是系統無響應時間超過5秒,便會出現ANR,APP可能會被強制關閉,所以,啓動時間做爲一個重要的性能指標,關係着用戶的第一體驗。java

愛奇藝安卓APP很是重視啓動速度的優化,本文將從啓動過程,啓動時間測量,啓動優化,以及後續監控等方面分享咱們在啓動優化方面積累的經驗。android

2 啓動模式

要準確的測量APP的啓動時間,首先咱們要了解APP整個啓動過程。 啓動過程,通常能夠分爲如下三類:shell

愛奇藝Android客戶端啓動優化與分析

從上圖能夠看出,啓動過程當中,Cold的模式下,生命週期中作的事情最多,啓動的時間最長,所以,咱們以冷啓動來衡量APP啓動時間。啓動過程當中,如何判斷哪些生命週期影響啓動速度呢?性能優化

3 啓動過程

咱們知道,APP的啓動和運行,就是Linux系統建立進程和組件對象,並在UI線程中處理組件消息的過程。網絡

啓動過程圖:多線程

App的啓動過程,能夠劃分爲三個階段:併發

3.1 建立進程

當APP啓動時,若是當前app的進程不存在,便會建立新的進程;App主進程啓動後,若是啓動某個組件,而且該組件設置了android:process屬性,組件所運行的進程不存在,也會建立新的進程。app

須要注意的是,若是在啓動階段,初始化的組件中,包含了多個進程,便會建立屢次進程,BindApplication操做也會重複執行屢次異步

3.2 建立UI線程及Handler

進程建立後,會經過反射,執行ActivityThread入口函數,建立Handler,並在當前線程中prepareMainLooper,並在Handler中接收組件的消息,咱們來看一下Handler中處理的消息:函數

  • LAUNCH_ACTIVITY,啓動,執行Activity
  • RESUME_ACTIVITY,恢復Activity
  • BIND_APPLICATION,啓動app
  • BIND_SERVICE,Service建立, onBind
  • LOW_MEMORY,內存不足,回收後臺程序

sMainThreadHandler中,處理的消息不少,這裏只羅列了,可能在啓動階段可能會執行的操做, 這些操做都是運行在Main Thread中,對啓動而言,屬於阻塞性的。

Activity生命週期,天然須要在啓動階段執行,但,對於Service的建立,Trim_memory回調,廣播接收等操做,就須要重點考慮,其操做耗時性。

3.3 Activity運行及繪製

前兩個過程,建立進程和UI線程及Handler,都是由系統決定的,對APP開發者而言,並不能控制其執行時間,在本階段,執行BindApplication,和Acitivity生命週期,都是能夠由開發者自定義。

Activity執行到onResume以後,會執行至ViewRootImpl,執行兩次performTraversals,第二次traversal操做中,會執行performDraw操做,同時通知RenderThread線程執行繪製.

從啓動的三個階段,咱們能夠看出,啓動啓動時間的長短,決定因素在於,主線程中所作事情消耗的時間的多少,因此,咱們的優化工做主要集中在,排查主線程中耗時性的工做,並進行合理的優化。Android手機,系統的資源是有限的,過多的異步線程,會搶佔CPU,致使主線程執行時間片間隔增大。一樣的,內存消耗狀態,GC頻率,也會影響啓動的時間。

4 分析及測量

經過上述的源碼的解讀,咱們已經瞭解了啓動過程,以及可能引發啓動過慢的緣由。接下來介紹一些經常使用的分析手段及時間測量方法。

I 啓動分析工具,主要使用SysTrace,具體的使用方法,請參考官網文檔developer.android.com/studio/comm…

4.1 SysTrace分析技巧

4.1.1 UI Thread 顏色顯示

  • 綠色:Running
  • 白色:Sleeping
  • 棕色:Uninterruptible Sleep
  • 橙色:Uninterruptible Sleep - Block I/O

其中10ms之內的,較短期的Sleeping狀態,不用關注,多是因爲CPU調度的時間片分配間隔引發的;較長時間的Block I/O和Sleep狀態,能夠肯定有阻塞啓動的邏輯在這個階段運行,須要進一步對代碼進行分析定位。

4.1.2 查看CPU狀態及線程運行時長

查看CPU佔用狀態:

線程執行:

經過該階段密集程度,反映出CPU佔用率,也能在必定程度上反映出該階段執行時間被阻塞狀況;線程執行狀況統計,能夠查看線程執行時間排名,對執行時間較長的子線程進行優化。

4.2 SysTrace啓動時間

在SysTrace圖中,UI Thread中包含了bindApplication,activityStart,traversal等操做,RenderThread中包含DrawFrame等操做。這些TAG節點是源碼已經添加的,可參考#3.2中介紹。

I Trace上啓動時間:從bindApplication至第二次traversal完成,可認爲UI第一次繪製完成,啓動完成。選中開始點和結束點,能夠查看過程消耗的時間。

4.3 adb shell am start -W

在統計APP啓動時間時,系統爲咱們提供了adb命令,能夠輸出啓動時間

I TotalTime: 表示新應用啓動的耗時,包括新進程的啓動和 Activity 的啓動,但不包括前一個應用 Activity pause 的耗時

系統在繪製完成後,ActivityManagerService會回調該方法,統計時間不如SysTrace準確,可是可以方便咱們經過腳本屢次啓動測量TotalTime,對比版本間啓動時間差別。

4.4 埋點

經過APP啓動生命週期中,關鍵位置加入時間點記錄,達到測量目的。

4.5 錄屏

錄屏方式收集到的時間,更接近於用戶的真實體感。

5 優化

爲了讓用戶在進入APP以後,更快更流暢的使用服務,因此會在啓動過程當中,提早對一些基礎庫和組建進行初始化操做,這就意味着系統有限的資源會被搶佔,影響啓動時間。啓動時間的優化,是一個平衡性能和體驗的過程。

經過Systrace工具分析,咱們發現愛奇藝愛奇藝安卓APP啓動過程當中一些問題,接下來,咱們就結合具體的業務實踐,進行啓動問題進行優化。

5.1 區分進程初始化Application

由#3咱們瞭解到,對於一個app而言,App內組件能夠運行在不一樣的進程之中。舉個例子: 一個APP擁有主進程,插件進程,下載進程三個進程,會在啓動階段建立相應的組件,但只有一個QYApplication繼承自系統Application,建立三次進程,QYApplication中attach(),onCreate()方法都會被執行三次。

每一個進程說須要初始化的內容確定是不同的,因此,爲了防止資源的浪費,咱們須要區分進程,初始化Appcation.

I 成果:對多進程應用而言,經過對初始化內容進行梳理,合理區分初始化,會大幅減小內存和CPU佔用。

5.2 異步處理耗時任務

子線程處理耗時任務,主線程作的事情越少,越早進入Acitivity繪製階段,界面越早展示。

注意:

  • 不在主線程作耗時任務,如文件,網絡等
  • 啓動階段初始化任務,儘可能在異步線程處理
  • 主線程,不用等待或者依賴於子線程任務

I 進一步優化:能夠自建線程池,維持必定線程個數,管理任務隊列。

5.3 防止多線程搶佔CPU

Android系統資源有限,特別是CPU資源,理論上來講,UI線程執行的任務,也沒法保證一直被調度狀態,當併發的線程數過多,UI線程時間片會更短,從而致使啓動時間被變慢。

下面羅列一些常見,容易形成CPU被搶佔的場景:

愛奇藝Android客戶端啓動優化與分析

I 成果:經過對執行時間較久,執行頻率的業務進行優化,將CPU佔有率維持在合理的程度,會大幅減小啓動時間,減小300ms以上。

5.4 系統API使用

部分系統的API使用是阻塞性的,文件很小可能沒法感知,當文件過大,或者使用頻繁時,可能形成阻塞。例如:

  • SharedPreference.Editor提交操做:
  1. commit方法屬於屬於阻塞性質API,建議使用apply。
  2. 此外,咱們知道,SP文件的存儲是一個XML文件,以key-value形式存儲,當業務過多時,須要拆分爲多個文件存儲,防止文件過大,出現讀取耗時及ANR。
  3. 進一步優化,可對啓動階段,頻繁的SP操做在內存中,統一提交。
  • AssetManager.open操做: Android開發中,咱們有時會將資源文件放在assets目錄中,而後使用open操做讀取文件,若是文件過大,須要在異步線程中執行。

I 成果:隨着業務量日積月累,正常的系統API的使用,也可能出現問題,經過排除,可減小50-100ms。

5.5 精簡佈局

佈局的複雜程度,直接影響繪製的時間。

舉個例子,在啓動過程當中,會有須要大的背景圖,只有第一次安裝時使用,後續屬性設置爲android:visibility="gone",可是,雖然設置了gone屬性,不會顯示,但依舊會被解析。

建議:

  • 減小布局層次
  • 無用資源使用ViewStub,使用時加載

I 成果:啓動階段的佈局較簡單,經過優化背景圖片的加載,減小50-100ms。

5.6 Service延後初始化

App啓動中過程當中,常常進行Service初始化操做,因爲Service使用通常不涉及界面,可能會認爲初始化生命週期不在主線程中,其實否則,在3.2的啓動過程源碼介紹中講到,Service的生命週期,也屬於主線程Handler接收的Message之一。

建議:Service生命週期中,注意邏輯執行時間性能優化,初始化儘可能延後。

I 成果:取決於初始化Service的生命週期執行時間,可減小200ms以上。

5.7 將任務delay至首頁繪製完成後

對於APP首頁展現不須要的初始化邏輯,可延後至首頁繪製完成後初始化。

注意:

  • 須要post兩次才能保證在第一次繪製以後顯示,由於,系統繪製會執行兩次Performtraversal。

進一步優化:可將業務邏輯的初始化劃分爲,首頁繪製後,5s,10s,20s三個階段分別初始化,防止首頁繪製執行任務過多形成掉幀。

I 成果:釋放繪製階段的CPU,可將複雜的繪製提早200ms以上。

6 監控

穩定的用戶體驗依賴於持續的監控,愛奇藝爲監控啓動性能創建了一套監控體系,測試,工具,開發等幾個團隊從不一樣的緯度搭建不一樣的監控方案

  • 1.測試:錄屏,從用戶的真實體驗角度,獲取最準確的啓動時間。
  • 2.實時監控:經過埋點,大數據採樣投遞獲取真實線上環境數據,從地域,時間,機型,app版本,系統版本等各個緯度對啓動時間進行監控。
  • 3.腳本測試:經過對腳本,對同一收集屢次啓動數據進行收集,經過不一樣版本間的對比,監控啓動時間的變化狀況。

7 SysTrace擴展

SysTrace經過TAG節點能夠清晰展示,啓動過程以及方法執行時間,可是,從發現問題,而後經過節點去定位問題,是一件很繁瑣的工做,若是大家工程編譯又比較慢,簡直讓人崩潰。

7.1 自動化TAG注入

在Android工程編譯的過程當中,指定class,在方法先後,自動化插入Trace節點,統計方法執行時間。

流程:

  • 1.在編譯的過程當中,插入自定義Task任務,
  • 2.讀取配置文件,文件中包含了須要注入java文件名和路徑名和method
  • 3.找到須要注入的class文件,而後經過ASM改變字節碼,方法先後,插入自定義自定義方法

經過工具的操做,可以作到不用修改原有工程文件,自動在打包時注入TAG節點和邏輯代碼,配置文件能夠循環利用,提升分析效率,節能環保。

8 優化結果

啓動時間,因爲不一樣的機型性能同,Android系統版本不一樣,同一APP版本啓動時間,相差很大,因此統計通常以同一手機,不一樣版本作比較,儘可能保證手機狀態一致。

SysTrace手機優化時間對比:

腳本屢次啓動時間收集對比:

通過多個版本的持續優化,有無廣告兩種不一樣的場景下,啓動時間分別減小40%和35%,啓動速度獲得了較大的提高。

9 總結

啓動時間的優化和監控,是一項長期的任務,須要對異常的狀況進行分析,對可能形成阻塞的代碼邏輯進行合理的優化,很是感謝各個業務團隊支持和配合。



+qq羣457848807:。獲取以上高清技術思惟圖,以及相關技術的免費視頻學習資料

相關文章
相關標籤/搜索