Android應用啟動優化:一種DelayLoad的實現和原理(下篇)

發表於2016-01-14

上一篇文章我們使用第三種方法來實現延遲載入。不過上一篇寫的比較簡單,只是講解了如何去實現,這一篇就來講一下為何要這麼做,以及這麼做後面的原理。
其中會涉及到一些 Android 中的比較重要的類,以及 Activity 生命週期中比較重要的幾個函式。
其實這個其中的原理比較簡單,不過要弄清楚其實現的過程,還是一件蠻好玩的事情,其中會用到一些工具,自己加除錯程式碼等,一步一步下來,自己對 Activity 的啟動的理解又深了一層,希望大家讀完之後也會對大家有一定的幫助。

 

上一篇中我們最終使用的 DelayLoad 的核心方法是在 Activity 的 onCreate 函式中加入下面的方法 :

我們一一來看涉及到的類和方法

1. Activity.getWindow 及 PhoneWindow 的初始化時機

Activity 的 getWindow 方法獲取到的是一個 PhoneWindow 物件:

這個 mWindow 就是一個 PhoneWindow 物件,其初始化的時機為這個 Activity attach 的時候:

這裡需要注意 Activity 的 attach 方法很早就會呼叫的,是要早於 Activity 的 onCreate 方法的。

總結:

  • PhoneWindow 與 Activity 是一對一的關係,通過上面的初始化過程你應該更加清楚這個概念
  • Android 中對 PhoneWindow 的註釋是 :Android-specific Window ,可見其重要性
  • PhoneWindow 中有很多大家比較熟悉的方法,比如 setContentView / addContentView 等 ; 也有幾個重要的內部類,比如:DecorView ;

2. PhoneWindow.getDecorView 及 DecorView 的初始化時機

上面我們說到 DecorView是 PhoneWindow 的一個內部類,其定義如下:

那麼 DecorView 是什麼時候初始化的呢?DecorView 是在 Activity 的父類的 onCreate 方法中被初始化的,比如我例子中的 MainActivity 是繼承自 android.support.v7.app.AppCompatActivity ,當我們呼叫 MainActivity 的 super.onCreate(savedInstanceState); 的時候,就會呼叫下面的

由於我們匯入的是 support.v7 包裡面的AppCompatActivity, getDelegate() 得到的就是AppCompatDelegateImplV7 ,其 onCreate 方法如下:

就是這裡的 mWindow.getDecorView() ,對 DecorView 進行了例項化:

第一次呼叫 getDecorView 的時候,會進入 installDecor 方法,這個方法對 DecorView 進行了一系列的初始化 ,其中比較重要的幾個方法有:generateDecor / generateLayout 等,generateLayout 會從當前的 Activity 的 Theme 提取相關的屬性,設定給 Window,同時還會初始化一個 startingView,新增到 DecorView上,也就是我們所說的 startingWindow。

總結

  • Decor 有裝飾的意思,DecorView 官方註釋為 “This is the top-level view of the window, containing the window decor” , 我們可以理解為 DecorView 是我們當前 Activity 的最下面的佈局。所以我們開啟 DDMS 檢視 Tree Overview 的時候,可以發現最根部的那個 View 就是 DecorView:
  • 應用從桌面啟動的時候,在主 Activity 還沒有顯示的時候,如果主題沒有設定視窗的背景,那麼我們就會看到白色(這個和手機的Rom也有關係),如果應用啟動很慢,那麼使用者得看好一會白色。如果要避免這個,則可以在 Application 或者 Activity 的 Theme 中設定 WindowBackground , 這樣就可以避免白色(當然現在各種大廠都是SplashActivity+廣告我也是可以理解的)

3. Post

當我們呼叫 DecorView 的 Post 的時候,其實最終會呼叫 View 的 Post ,因為 DecorView 最終是繼承 View 的:

注意這裡的 mAttachInfo ,我們呼叫 post 是在 Activity 的 onCreate 中呼叫的,那麼此時 mAttachInfo 是否為空呢?答案是 mAttachInfo 此時為空。

這裡有一個點就是 Activity 的各個回撥函式都是幹嘛的?是不是平時自己寫應用的時候,貌似在 onCreate 裡面搞定一切就OK了, onResume ? onStart?沒怎麼涉及到嘛,其實不然。
onCreate 顧名思義就是 Create ,我們在前面看到,Activity 的 onCreate 函式做了很多初始化的操作,包括 PhoneWindow/DecorView/StartingView/setContentView等,但是 onCreate 只是初始化了這些物件.
真正要設定為顯示則在 Resume 的時候,不過這些對開發者是透明瞭,具體可以看 ActivityThread 的 handleResumeActivity 函式,handleResumeActivity 中除了呼叫 Activity 的 onResume 回撥之外,還初始化了幾個比較重要的類:ViewRootImpl / ThreadedRenderer。

ActivityThread.handleResumeActivity:

主要是 wm.addView(decor, l); 這句,將 decorView 與 WindowManagerImpl聯絡起來,這句最終會呼叫到 WindowManagerGlobal 的 addView 函式,

我們知道 ViewRootImpl 是 View 系統的一個核心類,其定義如下:

ViewRootImpl 初始化的時候會對 AttachInfo 進行初始化,這就是為什麼之前的在 onCreate 的時候 attachInfo 為空。ViewRootImpl 裡面有很多我們比較熟悉也非常重要的方法,比如 performTraversals / performLayout / performMeasure / performDraw / draw 等。
我們繼續 addView 中的root.setView(view, wparams, panelParentView); 傳入的 view 為 decorView,root 為 ViewRootImpl ,這個函式中將 ViewRootImpl 的mView 變數 設定為傳入的view,也就是 decorView。
這樣來看,ViewRootImpl 與 DecorView 的關係我們也清楚了。

扯了一圈,我們再回到大標題的 Post 函式上,前面有說這個 Post 走的是 View 的Post 函式,由於 在 onCreate 的時候 attachInfo 為空,所以會走下面的分支:ViewRootImpl.getRunQueue().post(action);
注意這裡的 getRunQueue 得到的並不是 Looper 裡面的那個 MessageQueue,而是由 ViewRootImpl 維持的一個 RunQueue 物件,其核心為一個 ArrayList :

當我們執行了 Post 之後 ,其實只是把 Runnable 封裝成一個 HandlerAction 物件存入到 ArrayList 中,當執行到 executeActions 方法的時候,將存在這裡的 HandlerAction 再通過 executeActions 方法傳入的 Handler 物件重新進行 Post。
那麼 executeActions 方法是什麼時候執行的呢?傳入的 Handler 又是哪個 Handler 呢?

4. PerformTraversals

我們之前講過,ViewRootImpl 的 performTraversals 方法是一個很核心的方法,每一幀繪製都會走一遍,呼叫各種 measure / layout / draw 等 ,最終將要顯示的資料交給 hwui 去進行繪製。
我們上一節講到的 executeActions ,就是在 performTraversals 中執行的:

可以看到這裡傳入的 Handler 是 mAttachInfo.mHandler ,上一節講到 mAttachInfo 是在 ViewRootImpl 初始化的時候一起初始化的:

這裡的 mHandler 是一個 ViewRootHandler 物件:

我們注意到 ViewRootHandler 在建立的時候並沒有傳入一個 Looper 物件,這意味著此 ViewRootHandler 的 Looper 就是 mainLooper。

這下我們就清楚了,我們在 onCreate 中 Post 的 runnable 物件,最終還是在第一個 performTraversals 方法執行的時候,加入到了 MainLooper 的 MessageQueue 裡面了。

繞了一圈終於我們終於把文章最前面的那句話解釋清楚了,當然中間還有很多的廢話,不過我估計能耐著性子看到這裡的人會很少,所以如果你看到了這裡,可以在底下的評論裡面將 index ++ ;這裡 index = 0 ;就是看看幾個人是真正認真看了這篇文章的。

5. UpdateText

接著 performTraversals 我們繼續說,話說在第一篇文章 我們有講到,Activity 在啟動時,會在第二次執行 performTraversals 才會去真正的繪製,原因在於第一次執行 performTraversals 的時候,會走到 Egl 初始化的邏輯,然後會重新執行一次 performTraversals 。
所以前一篇文章的評論區有人問為何在 run 方法裡面還要 post 一次,如果在 run 方法裡面直接執行 updateText 方法 ,那麼 updateText 就會在第一個 performTraversals 之後就執行,而不是在第一幀繪製完成後才去執行,所以我們又 Post 了一次 。所以大概的處理步驟如下:

第一步:Activity.onCreate –> Activity.onStart –> Activity.onResume

第二步:ViewRootImpl.performTraversals –>Runnable

第三步:Runnable –> ViewRootImpl.performTraversals

第四步:ViewRootImpl.performTraversals –> UpdateText

第五步:UpdateText

6. 總結

其實一路跟下來發現其實原理很簡單,其實 DelayLoad 其實只是一個很小的點,關鍵是教大家如何去跟蹤一個自己不認識的知識點或者優化,這裡面主要用到了兩個工具:Systrace 和 Method Trace, 以及原始碼編譯和除錯。
關於 Systrace 和 Method Trace 的使用,之後會有詳細的文章去介紹,這兩個工具非常有助於理解原始碼和一些技術的實現。

Systrace

Method Trace

原始碼編譯與除錯

程式碼

本文章所所涉及到的程式碼我放到了Github上:
https://github.com/Gracker/DelayLoadSample

 

相關文章