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

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

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

1.ViewRootImpl
注釋截圖.png

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

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

代碼截圖.png

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

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

View的三大流程均是通過ViewRootImpl來完成的骑篙。ViewRootImpl的performTraversals方法會(huì)依次調(diào)用performMeasure蜕提、performLayout和performDraw三個(gè)方法,這三個(gè)方法分別完成頂級(jí)View的measure靶端、layout和draw這三大流程谎势,其中在performMeasure中會(huì)調(diào)用measure方法凛膏,在measure方法中又會(huì)調(diào)用onMeasure方法,在onMeasure方法中則會(huì)對(duì)所有的子元素進(jìn)行measure過程脏榆,這個(gè)時(shí)候measure流程就從父容器傳遞到子元素中了猖毫。接著子元素重復(fù)父容器的measure過程,如此反復(fù)完成了整個(gè)View樹的遍歷须喂。

ViewRootImpl不是View的子類吁断,其并不屬于View樹的一份子。ViewRootImpl可以理解為“View樹的管理者”——它有一個(gè)mView成員變量坞生,其指向Window和Activity中共同擁有的mDecor對(duì)象仔役,即View樹的根,DecorView是己。

2.DecorView

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

每個(gè)Activity都對(duì)應(yīng)一個(gè)窗口Window卒废,這個(gè)窗口是PhoneWindow的實(shí)例沛厨,PhoneWindow對(duì)應(yīng)的布局是DecorView。DecorView內(nèi)部又分為兩部分升熊,一部分是ActionBar俄烁,另一部分是ContentParent,即Activity在setContentView中對(duì)應(yīng)的布局级野。


Android視圖層級(jí).png

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

Step1

ActivityThread調(diào)用handleLaunchActivity方法啟動(dòng)一個(gè)Activity页屠,作為整個(gè)ViewTree建立流程的起點(diǎn)

Step2

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

①performLaunchActivity:

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

mWindow = PolicyManager.makeNewWindow(this);

這里的mWindow即是PhoneWindow對(duì)象潜索,它在每個(gè)Activity’中有且僅有一個(gè)實(shí)例

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

在setContentView方法內(nèi),會(huì)生成一個(gè)DecorView對(duì)象,并將加載的布局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方法的具體實(shí)現(xiàn)中,實(shí)例化了一個(gè)ViewRootImpl對(duì)象勉躺,并通過ViewRootImpl的setView方法將decorView添加到ViewRootImpl中癌瘾。

在ViewRootImpl實(shí)例化的過程中,會(huì)建立起ViewRootImpl與WMS間的Session連接饵溅;

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

View繪制的三大流程

1.measure

1.1.View的measure過程

View的measure過程由其measure方法來完成丐重,measure方法是一個(gè)final類型方法(意味著子類不能復(fù)寫)腔召。在View的measure方法中會(huì)調(diào)用View的onMeasure方法,onMeasure方法也是我們?cè)谧远xView時(shí)重點(diǎn)需要復(fù)寫的方法扮惦。

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

MeasureSpec表示View的尺寸規(guī)格浊仆,其代表著一個(gè)32位的int值。
其中高2位代表SpecMode豫领,即測(cè)量模式抡柿;低30位代表SpecSize,即該模式下的大小氏堤。
SpecMode有三類:UNSPECIFIED沙绝、EXACTLY和AT_MOST。其中UNSPECIFIED主要用于系統(tǒng)內(nèi)部鼠锈,此處可以不做考慮闪檬。EXACTLY表示View的MeasureSpec所提供的大小是精確值,也是View需要展現(xiàn)的大泄喊省粗悯;AT_MOST則表示View的MeasureSpec所提供的的大小是一個(gè)最大上限值,View的實(shí)際大小不能大于這個(gè)值同欠。

一個(gè)View的MeasureSpec是由其自身的LayoutParms和父容器的MeasureSpec來共同決定的样傍。

秘訣:
①當(dāng)View的布局參數(shù)是明確的大小(指定dp值)铺遂,該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過程,還會(huì)遍歷調(diào)用所有子元素的measure方法筝闹。

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

1.3.在Activity中獲取View的的測(cè)量寬高

由于View的measure過程和Activity的生命周期方法不是同步執(zhí)行的,所以無法保證Activity執(zhí)行onCreate聋伦、onStart夫偶、onResume時(shí)View已經(jīng)測(cè)量完畢了。如果View沒有測(cè)量完畢觉增,那么獲得的寬高大小就是0兵拢。
這里主要介紹兩種常用的方法。
①View.post(Runnable)
通過post方法可以將runnable投遞到主線程消息隊(duì)列的尾部逾礁,等Looper調(diào)用此runnable時(shí)说铃,View已經(jīng)完成初始化了访惜。因此可以在Runnable中獲取View的寬高。
②ViewTreeObserver
為ViewTreeObserver添加監(jiān)聽器OnGlobalLayoutListener腻扇,當(dāng)View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View可見性發(fā)生變化時(shí)债热,onGlobalLayout方法將會(huì)被回調(diào),因此這是獲取此View的寬高的一個(gè)好時(shí)機(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方法中會(huì)遍歷所有的子元素并調(diào)用其layout方法舶沿,在子元素的layout方法中子元素的onLayout方法又會(huì)接著被調(diào)用墙杯。

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

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

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

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

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

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

3.draw

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市紧唱,隨后出現(xiàn)的幾起案子活尊,更是在濱河造成了極大的恐慌隶校,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛹锰,死亡現(xiàn)場(chǎng)離奇詭異深胳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宁仔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峦睡,“玉大人翎苫,你說我怎么就攤上這事≌チ耍” “怎么了煎谍?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長龙屉。 經(jīng)常有香客問我呐粘,道長,這世上最難降的妖魔是什么转捕? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任作岖,我火速辦了婚禮,結(jié)果婚禮上五芝,老公的妹妹穿的比我還像新娘痘儡。我一直安慰自己,他們只是感情好枢步,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布沉删。 她就那樣靜靜地躺著,像睡著了一般醉途。 火紅的嫁衣襯著肌膚如雪矾瑰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天隘擎,我揣著相機(jī)與錄音殴穴,去河邊找鬼。 笑死货葬,一個(gè)胖子當(dāng)著我的面吹牛推正,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宝惰,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼植榕,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了尼夺?” 一聲冷哼從身側(cè)響起尊残,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤炒瘸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寝衫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顷扩,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年慰毅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隘截。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汹胃,死狀恐怖婶芭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情着饥,我是刑警寧澤犀农,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站宰掉,受9級(jí)特大地震影響呵哨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轨奄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一孟害、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挪拟,春花似錦纹坐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至球切,卻和暖如春谷誓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吨凑。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工捍歪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸵钝。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓糙臼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恩商。 傳聞我的和親對(duì)象是個(gè)殘疾皇子变逃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容