Android中的View Tree以及View的工作流程

在Android的知識體系中,View扮演著很重要的角色,負(fù)責(zé)向用戶展示UI并與用戶進(jìn)行交互。本文會先介紹與View相關(guān)的幾個基本概念,再對View的measure(測量)、layout(布局)、draw(繪制)三大流程進(jìn)行說明。

Android視圖架構(gòu)之ViewRootImpl和DecorView

1.ViewRootImpl
注釋截圖.png

如其代碼中所注釋的,ViewRootImpl是連接WindowManager和DecorView的紐帶。其主要功能有如下幾點(diǎn):

  • View樹的根并管理View樹
  • 觸發(fā)View的測量、布局和繪制
  • 輸入事件的中轉(zhuǎn)站
  • 管理Surface
  • 負(fù)責(zé)與WMS進(jìn)行進(jìn)程間通信

代碼截圖.png

ViewRootImpl有一個內(nèi)部類ViewRootHandler,其繼承自Handler,工作于主線程上,主要用于處理各類輸入事件,如觸摸事件、按鍵事件等。通過對輸入事件的處理,事件會被傳遞至Activity,隨后便以Activity作為起點(diǎn),開始事件的分發(fā)處理,如下圖所示。
事件分發(fā)流程圖(從硬件到Activity).png

事件分發(fā)流程圖(從硬件到View).png

View的三大流程均是通過ViewRootImpl來完成的。ViewRootImpl的performTraversals方法會依次調(diào)用performMeasure、performLayout和performDraw三個方法,這三個方法分別完成頂級View的measure、layout和draw這三大流程,其中在performMeasure中會調(diào)用measure方法,在measure方法中又會調(diào)用onMeasure方法,在onMeasure方法中則會對所有的子元素進(jìn)行measure過程,這個時候measure流程就從父容器傳遞到子元素中了。接著子元素重復(fù)父容器的measure過程,如此反復(fù)完成了整個View樹的遍歷。

ViewRootImpl不是View的子類,其并不屬于View樹的一份子。ViewRootImpl可以理解為“View樹的管理者”——它有一個mView成員變量,其指向Window和Activity中共同擁有的mDecor對象,即View樹的根,DecorView。

2.DecorView

DecorView是Android中ViewTree的根節(jié)點(diǎn),其繼承自FrameLayout。

每個Activity都對應(yīng)一個窗口Window,這個窗口是PhoneWindow的實例,PhoneWindow對應(yīng)的布局是DecorView。DecorView內(nèi)部又分為兩部分,一部分是ActionBar,另一部分是ContentParent,即Activity在setContentView中對應(yīng)的布局。


Android視圖層級.png

View Tree的創(chuàng)建過程

Step1

ActivityThread調(diào)用handleLaunchActivity方法啟動一個Activity,作為整個ViewTree建立流程的起點(diǎn)。

Step2

在handleLaunchActivity方法內(nèi)部,可以細(xì)分為performLaunchActivity(對應(yīng)onCreate)和handleResumeActivity(對應(yīng)onResume)兩個重要的子過程。

①performLaunchActivity:

該過程中,首先生成一個Activity對象,并調(diào)用它的attach方法。在attach方法中,系統(tǒng)會創(chuàng)建Activity所屬的Window對象并為其設(shè)置回調(diào)接口。

mWindow = PolicyManager.makeNewWindow(this);

這里的mWindow即是PhoneWindow對象,它在每個Activity’中有且僅有一個實例

隨后,就開始Activity的onCreate過程。在Activity的onCreate方法內(nèi),又會調(diào)用setContentView方法為Activity設(shè)置布局,這時將通過對應(yīng)的Window來完成DecorView的構(gòu)造。

在setContentView方法內(nèi),會生成一個DecorView對象,并將加載的布局xml文件添加進(jìn)decorView中。

②handleResumeActivity:

在剛才的Activity創(chuàng)建過程中,Activity內(nèi)部已經(jīng)完成了Window和DecorView的創(chuàng)建。接下來在onResume過程中,系統(tǒng)要將生成的視圖通過添加到WindowManagerGlobal的方式添加到WMS中。這里就開始涉及ViewRootImpl相關(guān)的操作。

該過程中,Activity的WindowManager以decorView為參數(shù)調(diào)用addView方法,在addView方法的具體實現(xiàn)中,實例化了一個ViewRootImpl對象,并通過ViewRootImpl的setView方法將decorView添加到ViewRootImpl中

在ViewRootImpl實例化的過程中,會建立起ViewRootImpl與WMS間的Session連接;

在setView方法的內(nèi)部實現(xiàn)中,ViewRootImpl會將decorView賦給自己的變量mView,decorView也會調(diào)用父類View的assignParent方法將ViewRootImpl賦值給自己的變量mParent,這樣decorView和ViewRoot之間就進(jìn)行了互相持有。setView方法中,還調(diào)用了requestlayout方法,開始了View Tree的第一次繪制。隨后,ViewRootImpl會通過之前獲得的windowSession的addToDisplay方法將一個IWindow的Binder對象傳遞給WMS,WMS便能通過IWindow對ViewRoot進(jìn)行操作。這樣就形成了ViewRootImpl和WMS的雙向通信通道。

View繪制的三大流程

1.measure

1.1.View的measure過程

View的measure過程由其measure方法來完成,measure方法是一個final類型方法(意味著子類不能復(fù)寫)。在View的measure方法中會調(diào)用View的onMeasure方法,onMeasure方法也是我們在自定義View時重點(diǎn)需要復(fù)寫的方法。

onMeasure方法有兩個參數(shù):widthMeasureSpec和heightMeasureSpec,分別表示寬度測量規(guī)格和高度測量規(guī)格,它們都對應(yīng)著同一個類——MeasureSpec。

MeasureSpec表示View的尺寸規(guī)格,其代表著一個32位的int值。
其中高2位代表SpecMode,即測量模式;低30位代表SpecSize,即該模式下的大小。
SpecMode有三類:UNSPECIFIED、EXACTLY和AT_MOST。其中UNSPECIFIED主要用于系統(tǒng)內(nèi)部,此處可以不做考慮。EXACTLY表示View的MeasureSpec所提供的大小是精確值,也是View需要展現(xiàn)的大小;AT_MOST則表示View的MeasureSpec所提供的的大小是一個最大上限值,View的實際大小不能大于這個值。

一個View的MeasureSpec是由其自身的LayoutParms和父容器的MeasureSpec來共同決定的。

秘訣:
①當(dāng)View的布局參數(shù)是明確的大?。ㄖ付╠p值),該View的SpecMode一律為EXACTLY,并且SpecSize值為View自身布局參數(shù)中設(shè)置的大小。
②當(dāng)View的布局參數(shù)是match_parent,則其SpecMode和父容器的SpecMode相同,SpecSize為父容器的大小。
③當(dāng)View的布局參數(shù)是wrap_content,該View的SpecMode一律為AT_MOST,且其SpecSize為其父容器的大小。

在View的onMeasure方法中,通過調(diào)用setMeasuredDimension方法,即完成了measure過程。

1.2.ViewGroup的measure過程

ViewGroup除了要完成自己的measure過程,還會遍歷調(diào)用所有子元素的measure方法。

由于不同的ViewGroup有不同的布局特性,這導(dǎo)致它們的測量細(xì)節(jié)各不相同。ViewGroup是個抽象類,它自身并未重寫View的onMeasure方法,它的測量過程需要其各個子類去具體實現(xiàn),如LinearLayout、RelativeLayout等。

1.3.在Activity中獲取View的的測量寬高

由于View的measure過程和Activity的生命周期方法不是同步執(zhí)行的,所以無法保證Activity執(zhí)行onCreate、onStart、onResume時View已經(jīng)測量完畢了。如果View沒有測量完畢,那么獲得的寬高大小就是0。
這里主要介紹兩種常用的方法。
①View.post(Runnable)
通過post方法可以將runnable投遞到主線程消息隊列的尾部,等Looper調(diào)用此runnable時,View已經(jīng)完成初始化了。因此可以在Runnable中獲取View的寬高。
②ViewTreeObserver
為ViewTreeObserver添加監(jiān)聽器OnGlobalLayoutListener,當(dāng)View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View可見性發(fā)生變化時,onGlobalLayout方法將會被回調(diào),因此這是獲取此View的寬高的一個好時機(jī)。

    @Override
    protected void onStart() {
        super.onStart();
        ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }

2.layout

Layout的作用是ViewGroup用來確定子元素的位置,當(dāng)ViewGroup的位置被確定后,它在onLayout方法中會遍歷所有的子元素并調(diào)用其layout方法,在子元素的layout方法中子元素的onLayout方法又會接著被調(diào)用。

在layout方法中,首先通過setFrame方法來設(shè)定View的四個頂點(diǎn)的位置,View的四個頂點(diǎn)一旦確定,其在父容器中的位置也就確定了。接著會調(diào)用onLayout方法,用來確定子元素的位置,這個方法在View和ViewGroup中都沒有實現(xiàn),需要根據(jù)子類的需求去具體實現(xiàn)。

總之,layout方法用來確定自身位置,onLayout方法用來確定所有子元素的位置。

View的測量寬高和最終寬高的區(qū)別?

View的測量寬高對應(yīng)View的getMeasuredWidth/Height方法,最終寬高則對應(yīng)View的getWidth/Height方法。

在View的默認(rèn)實現(xiàn)中,View的測量寬高和最終寬高是相等的,只不過測量寬高形成于View的measure過程,最終寬高形成于View的layout過程,兩者賦值時機(jī)不同而已。

例外:可以通過重寫View的layout方法來強(qiáng)行改變View的最終寬高。

3.draw

Draw的作用是將View繪制到屏幕上。
通過瀏覽View的draw方法,可以看出View的繪制過程遵循如下幾步:

  1. 繪制背景
  2. 繪制自己(onDraw)
  3. 繪制子元素
  4. 繪制裝飾

其中第二步,通過調(diào)用onDraw方法繪制自己的內(nèi)容,這也是在自定義View時非常重要的需要復(fù)寫的方法。
onDraw方法中的內(nèi)容需要根據(jù)視圖的特征去具體實現(xiàn),這方面內(nèi)容在自定義View中再做詳細(xì)說明。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容