UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

yilian發表於2020-01-10

前言

在android當中對於UI體系當中往往我們會在繪製UI的時候碰到各種各樣的問題而不知道從何解決, 也有時需要開發更改自定義元件時,需要做自己的調整,或者是實現某個自定義特效時的思路不明確,想要達到去玩轉UI的最為基礎的部分,就是去全面的深入瞭解UI的繪製流程.所以接下來帶大家去進行全面分析UI整體的繪製體系.

思路:android程式啟動-→Activity載入並完成生命週期-→setContentView-→圖形繪製

疑惑:

1.Android程式是如何啟動,Activity生命週期如何呼叫?

2.在Activity onCreate當中我們的setContentView是如何將UI檔案載入?

3.UI是如何繪製的?

答案:

1.Android程式流程

眾所周知,我們的java程式想要開啟需要依賴於main方法,也就是我們的程式入口(主執行緒)進入,但是在我們日常開發android程式的過程當中我們並沒有發現main方法的存在,那麼android當中的是如何開始執行的?

熟悉的朋友們可能都知道在android當中存在一個叫做ActivityThread的類,這個類代表的是android當中的主執行緒,而在這個類當中我們看到了比較熟悉的main方法,那麼現在是否可以認為我們的android在開啟app時是首先呼叫的是當前這個類的main,也就是此處為我們的啟動點

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

在此處可以看到Activity呼叫了一個attach()方法

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

在這裡我們可能首先要考慮的是getService拿出來的是什麼?

進去之後,我們會發現

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

在這個當中,裡面呼叫了的系統的ActivityManagerService這個服務,並且給出了一個Binder介面

那麼在這裡,我們可以聯想到,在android當中的binder通訊機制,那麼實際上我們的ActivityManager是有系統服務所呼叫管理,並且透過在binder介面當中進行呼叫,這也是為什麼我們講Activity是跨程式訪問的原因

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

那麼明白了這個時候能夠得到ActivityManager之後,我們接著回到attach當中繼續看下去, 這個時候會發現,我們呼叫了一個attachApplication方法(見圖2)這個方法又是幹嘛的?attachApplication在這裡的作用其實實際上是ActivityThread透過attach獲取到,然後將applciationThread將其關聯,把activity相關資訊儲存在applciationThread裡面,apllicationThread的類為activity的各種狀態做了相對應的準備工作

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

這個時候我們需要關注,ApplicationThread當中做了什麼?

當我們開啟ApplicationThread中我們會看到一堆的schedle方法,這些方法的名稱其實就可以給我們表明,代表的是在執行Activity的某種狀態時呼叫的計劃執行方法

這時我們會看到一個scheduleLaunchActivity方法,表示計劃載入時呼叫的

這裡我發現了一個很有意思的事情

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!
UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

這個上面我們會看到一個ActivityClientRecord物件,這個物件其實實際上就是我們的Activity

而且似乎每一個方法還幹了一件讓我們非常熟悉的一件事, 進行了一次sendMessage()將當前建立的Activity傳送了出去

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

當走到這裡我們會發現最終我們呼叫的是Handler的訊息通訊機制,也就是說,在這裡我們可以總結一下,

當Activity狀態改變時,都會有對應的一個訊息傳送出去

而接收這裡,我能發現透過傳送時不同的狀態,這邊呼叫了不同的handlerXXXActivity方法

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

在這裡,我門貌似發現了Activity的生命週期的呼叫痕跡,那麼其實到此為止,我門可以得出一個結論,

Application執行的過程當中,對於Activity的操作,狀態轉變,其實實際上是透過Handler訊息機制來完成的,

Application當中只管去發, 由訊息機制負責呼叫,因為在main方法當中我門的Looper輪訓器是一直在進行輪訓的

而當我們在載入Activity的時候,當中呼叫了一個performLaunchActivity()方法,在這個中間我發現了我們onCreate的呼叫痕跡

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

也就是說,到目前為止我們能夠明白,整個Application載入Activity的整套流程是怎麼回事

那麼接下來我們需要關注的是,在onCreate當中我們所寫的setContentView到底幹了什麼

2.setContentView

在onCreate當中我們往往會使用setContentView去進行設定我們自己的佈局檔案或者view,那麼在這當中他到底是怎麼做的?透過觀察原始碼,這個時候透過一系列線索我找到了最終的位置PhoneWindow類

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

這個時候我們會看到他做了兩個事情,一個是installDecor,另一個是inflate,這兩個後一個不難猜出他是在進行佈局檔案的解析, 前面的我們認為她是在初始化某個東西

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

進來之後發現他初始化了兩個東西,一個叫做mDecor,一個叫做mContentParent

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!
UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

我們看到了mDecor是一個DecorView

mContentParent是一個ViewGroup

透過註釋的翻譯,其實我們就能很明確知道這兩個是用來幹嘛的

// This is the view in which the window contents are placed. It is either(這是放置視窗內容的檢視)

// mDecor itself, or a child of mDecor where the contents go.(它要麼是mDecor本身,要麼是mDecor的子類的內容。)

//This is the top-level view of the window, containing the window decor.( 這是在視窗當中的頂層View,包含視窗的decor)

一個代表的是頂層view,一個用來裝他下面的檢視內容

在接著往下看的時候,我門發現,generateLayout方法當中,發現了在此處進行了大量的requestFeature的呼叫,也就是所,我們的requestFeature

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

然後在下面我門會發現在做了一件事情,

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!
UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!
UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

當前這裡竟然在載入佈局檔案,並且生成了一個view, 但是好像貌似不是我門自己的

所以我們需要去探尋他到底載入了一個什麼東東?

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

這是我找到了一個比較有意思的元件,

在這個上面我看到了一句這樣的註釋

//This is an optimized layout for a screen, with the minimum set of features

enabled.

這是一個螢幕的最佳化佈局,具有最小的特徵集啟用。

透過註釋和一些資料分析, 得到了一個比較坑的結果。

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

這是DecorView預設的一個渲染,然後我門自己的佈局都是渲染到她的FrameLayout上的

那麼在這裡我門現在能夠明白,installDector其實實際上是在初始化兩個檢視容器,然後載入系統的R資源及特徵,產生了一個基本佈局

那麼接著回到之前我門關注的另外一個方法mLayoutInflater.inflate(layoutResID, mContentParent);

這個方法就比較好理解了,

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

這這段註釋上面我門就可以得到一個資訊

//Inflate a new view hierarchy from the specified xml resource.(從指定的檢視當中獲取試圖的層次結構,意思就是,現在在載入自己的資源)

而具體流程就不貼程式碼了給各位上一張圖

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

那麼在這裡我門就能夠明白,setContentView其實做了兩件比較核心的事情,就是載入環境配置,和自己的佈局,那麼接下來我門需要考慮的事情就是,他到底怎麼畫到介面上的

3.UI是如何繪製的?

透過前面兩個章節,我門瞭解到,程式對於activity生命週期的呼叫,以及我們的檢視資源的由來。這是我門需要找到的是我門的繪製起點在哪?

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

在ActivityThread啟動時, 我發現在載入handleLaunchActivity方法呼叫performLaunchActivity方法之後又呼叫了一個handleResumeActivity在這裡我發現了繪製流程的開始

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

透過前面的流程我門知道,onCreate之行完成之後,所有資源交給WindowManager保管

在這裡,將我們的VIew交給了WindowManager,此處呼叫了addView

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!
UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!
UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

進入addView之後我們發現了一段這樣的程式碼,他將檢視,和引數還有我門的一個ViewRoot物件都用了容器去裝在了起來,那麼在此處我門可以得出,是將所有的相關物件儲存起來

mViews儲存的是View物件,DecorView

mRoots儲存和頂層View關聯的ViewRootImpl物件

mParams儲存的是建立頂層View的layout引數。

而WindowManagerGlobal類也負責和WMS通訊

而在此時,有一句關鍵程式碼root.setView,這裡是將我們的引數,和檢視同時交給了ViewRoot,那麼這個時候我們來看下ViewRoot當中的setView幹了什麼

終於在這裡讓我發現了讓我明白的一步

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

在這裡我門會看到view.assignParent的設定是this, 那麼也就是說在view當中parent其實實際上是ViewRoot

那麼在setContentView當中呼叫了一個setLayoutParams()是呼叫的ViewRoot的

而在ViewRoot當中發現了setLayoutParams和preformLayout對requestLayout方法的呼叫

在requestLayout當中發現了對scheduleTraversals方法的呼叫而scheduleTraversals當中呼叫了doTraversal的訪問,最終訪問到了performTraversals(),而在這個裡面,我發現了整體的繪製流程的呼叫

當前裡面依次是用了

UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!
UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!
UI繪製流程,讓無數安卓工程師無從下手?一篇文章就教你讀懂!

UI繪製先回去測量佈局,然後在進行佈局的擺放,當所有的佈局測量擺放完畢之後,進行繪製。

至此整體UI繪製過程我們就已經非常清楚了。

我們可以根據這種繪製的流程來操作自己的自定義元件。

BATJ、位元組跳動面試專題,演算法專題,高階技術專題,混合開發專題,java面試專題,Android,Java小知識,到效能最佳化.執行緒.View.OpenCV.NDK等已經上傳到了的我的GitHub

大家點選我的GitHub地址: 點下star一起學習


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2672837/,如需轉載,請註明出處,否則將追究法律責任。

相關文章