前言
? ? ? ?自定義View、多線程、網(wǎng)絡(luò),被認(rèn)為是Android開(kāi)發(fā)者必須牢固掌握的最基礎(chǔ)的三大基本功。Android View的繪制流程原理又是學(xué)好自定義View的理論基礎(chǔ),所以掌握好View的繪制原理是Android開(kāi)發(fā)進(jìn)階中無(wú)法繞過(guò)的一道坎。而關(guān)乎到原理性的東西往往又讓很多初學(xué)者感到難以下手,所以真正掌握的人并不多。本文采用非常淺顯的語(yǔ)言,從順著Android源碼的思路,對(duì)View的整個(gè)繪制流程進(jìn)行近乎“地毯式搜索”般的方式,對(duì)其中的關(guān)鍵流程和知識(shí)點(diǎn)進(jìn)行查證和分析,以圖讓初級(jí)程序員都能輕松讀懂。本文最大的特點(diǎn),就是最大限度地向源碼要答案,從源碼中追流程的來(lái)龍去脈,在注釋中查功能的點(diǎn)點(diǎn)滴滴,所有的結(jié)論都盡量在源碼和注釋中找根據(jù)。
? ? ? ?為了能對(duì)其中的重難點(diǎn)分析透徹,文中貼出了大量的源碼依據(jù)以及源碼中的注釋,并對(duì)重要的注釋進(jìn)行了翻譯和講解,所以文章會(huì)比較長(zhǎng)。講解該知識(shí)點(diǎn)的文章普遍都非常長(zhǎng),所以希望讀者能夠秉承程序員吃苦耐勞的精神,攻克這個(gè)難關(guān)。本文中的源碼是基于API26的,即Android8.0系統(tǒng)版本,主要內(nèi)容大致如下:
一、View繪制的三個(gè)流程
? ? ? ?我們知道,在自定義View的時(shí)候一般需要重寫父類的onMeasure()、onLayout()、onDraw()三個(gè)方法,來(lái)完成視圖的展示過(guò)程。當(dāng)然,這三個(gè)暴露給開(kāi)發(fā)者重寫的方法只不過(guò)是整個(gè)繪制流程的冰山一角,更多復(fù)雜的幕后工作,都讓系統(tǒng)給代勞了。一個(gè)完整的繪制流程包括measure、layout、draw三個(gè)步驟,其中:
? ? ?measure:測(cè)量。系統(tǒng)會(huì)先根據(jù)xml布局文件和代碼中對(duì)控件屬性的設(shè)置,來(lái)獲取或者計(jì)算出每個(gè)View和ViewGrop的尺寸,并將這些尺寸保存下來(lái)。
? ? ?layout:布局。根據(jù)測(cè)量出的結(jié)果以及對(duì)應(yīng)的參數(shù),來(lái)確定每一個(gè)控件應(yīng)該顯示的位置。
? ? ?draw:繪制。確定好位置后,就將這些控件繪制到屏幕上。
二、Android視圖層次結(jié)構(gòu)簡(jiǎn)介
? ? ? ?在介紹View繪制流程之前,咱們先簡(jiǎn)單介紹一下Android視圖層次結(jié)構(gòu)以及DecorView,因?yàn)閂iew的繪制流程的入口和DecorView有著密切的聯(lián)系。
?咱們平時(shí)看到的視圖,其實(shí)存在如上的嵌套關(guān)系。上圖是針對(duì)比較老的Android系統(tǒng)版本中制作的,新的版本中會(huì)略有出入,還有一個(gè)狀態(tài)欄,但整體上沒(méi)變。我們平時(shí)在Activity中setContentView(...)中對(duì)應(yīng)的layout內(nèi)容,對(duì)應(yīng)的是上圖中ViewGrop的樹(shù)狀結(jié)構(gòu),實(shí)際上添加到系統(tǒng)中時(shí),會(huì)再裹上一層FrameLayout,就是上圖中最里面的淺藍(lán)色部分了。
? ? ? ?這里咱們?cè)偻ㄟ^(guò)一個(gè)實(shí)例來(lái)繼續(xù)查看。AndroidStudio工具中提供了一個(gè)布局視察器工具,通過(guò)Tools > Android > Layout Inspector可以查看具體某個(gè)Activity的布局情況。下圖中,左邊樹(shù)狀結(jié)構(gòu)對(duì)應(yīng)了右邊的可視圖,可見(jiàn)DecorView是整個(gè)界面的根視圖,對(duì)應(yīng)右邊的紅色框,是整個(gè)屏幕的大小。黃色邊框?yàn)闋顟B(tài)欄部分;那個(gè)綠色邊框中有兩個(gè)部分,一個(gè)是白框中的ActionBar,對(duì)應(yīng)了上圖中紫色部分的TitleActionBar部分,即標(biāo)題欄,平時(shí)咱們可以在Activity中將其隱藏掉;另外一個(gè)藍(lán)色邊框部分,對(duì)應(yīng)上圖中最里面的藍(lán)色部分,即ContentView部分。下圖中左邊有兩個(gè)藍(lán)色框,上面那個(gè)中有個(gè)“contain_layout”,這個(gè)就是Activity中setContentView中設(shè)置的layout.xml布局文件中的最外層父布局,咱們能通過(guò)layout布局文件直接完全操控的也就是這一塊,當(dāng)其被add到視圖系統(tǒng)中時(shí),會(huì)被系統(tǒng)裹上ContentFrameLayout(顯然是FrameLayout的子類),這也就是為什么添加layout.xml視圖的方法叫setContentView(...)而不叫setView(...)的原因。
三、故事開(kāi)始的地方
? ? ? ? 如果對(duì)Activity的啟動(dòng)流程有一定了解的話,應(yīng)該知道這個(gè)啟動(dòng)過(guò)程會(huì)在ActivityThread.java類中完成,在啟動(dòng)Activity的過(guò)程中,會(huì)調(diào)用到handleResumeActivity(...)方法,關(guān)于視圖的繪制過(guò)程最初就是從這個(gè)方法開(kāi)始的。
? 1、View繪制起源UML時(shí)序圖
? ? ? ?整個(gè)調(diào)用鏈如下圖所示,直到ViewRootImpl類中的performTraversals()中,才正式開(kāi)始繪制流程了,所以一般都是以該方法作為正式繪制的源頭。
?2、handleResumeActivity()方法
? ? ? ?在這咱們先大致看看ActivityThread類中的handleResumeActivity方法,咱們這里只貼出關(guān)鍵代碼:
上述代碼第8行中,ViewManager是一個(gè)接口,addView是其中定義個(gè)一個(gè)空方法,WindowManager是其子類,WindowManagerImpl是WindowManager的實(shí)現(xiàn)類(順便啰嗦一句,這種方式叫做面向接口編程,在父類中定義,在子類中實(shí)現(xiàn),在Java中很常見(jiàn))。第4行代碼中的r.window的值可以根據(jù)Activity.java的如下代碼得知,其值為PhoneWindow實(shí)例。
3、兩個(gè)重要參數(shù)分析
? ? ? ?之所以要在這里特意分析handleResumeActivity()方法,除了因?yàn)樗钦麄€(gè)繪制流程的最初源頭外,還有就是addView的兩個(gè)參數(shù)比較重要,它們經(jīng)過(guò)一層一層傳遞后進(jìn)入到ViewRootImpl中,在后面分析繪制中要用到。這里再看看這兩個(gè)參數(shù)的相關(guān)信息:
? ? (1)參數(shù)decor
可見(jiàn)decor參數(shù)表示的是DecorView實(shí)例。注釋中也有說(shuō)明:這是window的頂級(jí)視圖,包含了window的decor。
? ? (2)參數(shù)l
該參數(shù)表示l的是PhoneWindow的LayoutParams屬性,其width和height值均為L(zhǎng)ayoutParams.MATCH_PARENT。在源碼中,WindowPhone和DecorView通過(guò)組合方式聯(lián)系在一起的,而DecorView是整個(gè)View體系的根View。在前面handleResumeActivity(...)方法代碼片段中,當(dāng)Actiivity啟動(dòng)后,就通過(guò)第14行的addView方法,來(lái)間接調(diào)用ViewRootImpl類中的performTraversals(),從而實(shí)現(xiàn)視圖的繪制。
四、主角登場(chǎng)?
? 無(wú)疑,performTraversals()方法是整個(gè)過(guò)程的主角,它把控著整個(gè)繪制的流程。該方法的源碼有大約800行,這里咱們僅貼出關(guān)鍵的流程代碼,如下所示:
上述代碼中就是一個(gè)完成的繪制流程,對(duì)應(yīng)上了第一節(jié)中提到的三個(gè)步驟:
? ? ? 1)performMeasure():從根節(jié)點(diǎn)向下遍歷View樹(shù),完成所有ViewGroup和View的測(cè)量工作,計(jì)算出所有ViewGroup和View顯示出來(lái)需要的高度和寬度;
? ? ? 2)performLayout():從根節(jié)點(diǎn)向下遍歷View樹(shù),完成所有ViewGroup和View的布局計(jì)算工作,根據(jù)測(cè)量出來(lái)的寬高及自身屬性,計(jì)算出所有ViewGroup和View顯示在屏幕上的區(qū)域;
? ? ? 3)performDraw():從根節(jié)點(diǎn)向下遍歷View樹(shù),完成所有ViewGroup和View的繪制工作,根據(jù)布局過(guò)程計(jì)算出的顯示區(qū)域,將所有View的當(dāng)前需顯示的內(nèi)容畫到屏幕上。
咱們后續(xù)就是通過(guò)對(duì)這三個(gè)方法來(lái)展開(kāi)研究整個(gè)繪制過(guò)程。
五、measure過(guò)程分析
? ? ? ?這三個(gè)繪制流程中,measure是最復(fù)雜的,這里會(huì)花較長(zhǎng)的篇幅來(lái)分析它。本節(jié)會(huì)先介紹整個(gè)流程中很重要的兩個(gè)類MeasureSpec和ViewGroup.LayoutParams類,然后介紹ViewRootImpl、View及ViewGroup中測(cè)量流程涉及到的重要方法,最后簡(jiǎn)單梳理DecorView測(cè)量的整個(gè)流程并鏈接一個(gè)測(cè)量實(shí)例分析整個(gè)測(cè)量過(guò)程。
? 1、MeasureSpec簡(jiǎn)介
? ? ? ?這里咱們直接上源碼吧,先直接通過(guò)源碼和注釋認(rèn)識(shí)一下它,如果看不懂也沒(méi)關(guān)系,在后面使用的時(shí)候再回頭來(lái)看看。
?從這段代碼中,咱們可以得到如下的信息:
? ? 1)MeasureSpec概括了從父布局傳遞給子view布局要求。每一個(gè)MeasureSpec代表了寬度或者高度要求,它由size(尺寸)和mode(模式)組成。
? ? 2)有三種可能的mode:UNSPECIFIED、EXACTLY、AT_MOST
? ? 3)UNSPECIFIED:未指定尺寸模式。父布局沒(méi)有對(duì)子view強(qiáng)加任何限制。它可以是任意想要的尺寸。(筆者注:這個(gè)在工作中極少碰到,據(jù)說(shuō)一般在系統(tǒng)中才會(huì)用到,后續(xù)會(huì)講得很少)
? ? 4)EXACTLY:精確值模式。父布局決定了子view的準(zhǔn)確尺寸。子view無(wú)論想設(shè)置多大的值,都將限定在那個(gè)邊界內(nèi)。(筆者注:也就是layout_width屬性和layout_height屬性為具體的數(shù)值,如50dp,或者設(shè)置為match_parent,設(shè)置為match_parent時(shí)也就明確為和父布局有同樣的尺寸,所以這里不要以為筆者搞錯(cuò)了。當(dāng)明確為精確的尺寸后,其也就被給定了一個(gè)精確的邊界)
? ? 5)AT_MOST:最大值模式。子view可以一直大到指定的值。(筆者注:也就是其寬高屬性設(shè)置為wrap_content,那么它的最大值也不會(huì)超過(guò)父布局給定的值,所以稱為最大值模式)
? ? 6)MeasureSpec被實(shí)現(xiàn)為int型來(lái)減少對(duì)象分配。該類用于將size和mode元組裝包和拆包到int中。(筆者注:也就是將size和mode組合或者拆分為int型數(shù)據(jù))
? ? 7)分析代碼可知,一個(gè)MeasureSpec的模式如下所示,int長(zhǎng)度為32位置,高2位表示mode,后30位用于表示size
? ? ?8)UNSPECIFIED、EXACTLY、AT_MOST這三個(gè)mode的示意圖如下所示:
?9)makeMeasureSpec(int mode,int size)用于將mode和size打包成一個(gè)int型的MeasureSpec。
? ? 10)getSize(int measureSpec)方法用于從指定的measureSpec值中獲取其size。
? ? 11)getMode(int measureSpec)方法用戶從指定的measureSpec值中獲取其mode。
2、ViewGroup.LayoutParams簡(jiǎn)介
? ?該類的源碼及注釋分析如下所示。
這對(duì)其中重要的信息做一些翻譯和整理:
? ? 1)LayoutParams被view用于告訴它們的父布局它們想要怎樣被布局。(筆者注:字面意思就是布局參數(shù))
? ? 2)該LayoutParams基類僅僅描述了view希望寬高有多大。對(duì)于每一個(gè)寬或者高,可以指定為以下三種值中的一個(gè):MATCH_PARENT,WRAP_CONTENT,an exact number。(筆者注:FILL_PARENT從API8開(kāi)始已經(jīng)被MATCH_PARENT取代了,所以下文就只提MATCH_PARENT)
? ? 3)MATCH_PARENT:意味著該view希望和父布局尺寸一樣大,如果父布局有padding,則要減去該padding值。
? ? 4)WRAP_CONTENT:意味著該view希望其大小為僅僅足夠包裹住其內(nèi)容即可,如果自己有padding,則要加上該padding值。
? ? 5)對(duì)ViewGroup不同的子類,也有相應(yīng)的LayoutParams子類。?
? ? 6)其width和height屬性對(duì)應(yīng)著layout_width和layout_height屬性。
轉(zhuǎn)載請(qǐng)申明轉(zhuǎn)自【https://www.cnblogs.com/andy-songwei/p/10955062.html】