迎面走來的一位中年男子,他一手拿着保溫杯,一手抱着筆記本電腦,頂着惺忪的睡眼,不緊不慢地走着,很少的幾根頭髮在他頭頂自由飛翔。過了一會,他面對着我坐下,放下電腦和保溫杯,邊揉眉頭邊對我說web
「來面試的?」面試
「對對對」 我趕忙答應算法
「行吧,那你講講 View 的繪製流程吧」markdown
View 的繪製流程應該是每一個初高級 Android 攻城獅必知必會的東西,也是面試必考的內容,每一個人都有不一樣的回答方式。svg
簡單點譬如 measure,layout,draw 分別對應測量,佈局,繪製三個過程,高明一點的會引伸出 Handler,同步屏障,View 的事件傳遞,甚至 activity 的啓動過程。掌握哪些東西,如何回答,可以給面試官一種清晰,瞭然於胸的感受,同時又不會被追問三連一問三不知。各位老爺聽我慢慢道來。函數
「噢噢,View 的繪製啊。這個能夠分爲頂級 View 的繪製,Viewgroup 的繪製和 View 的繪製三個方面。頂級 View 就是 ViewrootImpl」佈局
將回答的內容分類是體現本身思考能力和知識結構的重要表現。post
相比 Viewgroup 和 View,ViewRootImpl 可能更爲陌生,實際開發中咱們基本用不到它。那麼優化
什麼是 ViewRootImpl 呢?動畫
從結構上來看,ViewRootImpl 和 ViewGroup 實際上是一種東西
它們都繼承了 ViewParent。ViewParent 是一個接口,定義了一些父 View 的基本行爲,好比 requestlayout,getparent 等。不一樣的是,ViewRootImpl 並不會像 ViewGroup 同樣被真正繪製在屏幕上。在 activity 中,它是專門用來繪製 DecorView 的,核心方法是 setView
提到 DecorView,就不得不說一下 window 了。面試中經常咱們提到一個點,或者一個詞,面試官會立刻引伸出這個知識點相關的問題。若是咱們只是死記硬背,自顧自背一堆繪製相關的東西而回答不上來,會大大減分。因此儲備與必問內容相關的東西對面試和本身的知識體系頗有幫助。很多老爺被面試的時候都會被問到一個問題
「activity,window,View 三者之間的關係是什麼?」
咱們能夠經過一張圖來講明。
如圖所示,window 是 activity 裏的一個實例變量,本質是一個抽象類,惟一的實現類是 PhoneWindow。
activity 的 setContentView 方法其實是就是交給 phonewindow 去作的。window 和 View 的關係能夠類比爲顯示器和顯示的內容。
每一個 activity 都有一個「顯示器」 window,「顯示的內容」就是 DecorView。這個「顯示器」定義了一些方法來決定如何顯示內容。好比 setTitleColor setTitle 是設置導航欄的顏色和 title , setAllowReturnTransitionOverlap 設置進/出場動畫等等。
因此 window 是 activity 的一個成員變量,window 和 View 是「顯示器」和「顯示內容」的關係。
這就是他們的關係
「呦呵,不錯嘛,這個比喻不錯,看來平時還挺愛思考的。行,你繼續說說 View 是怎麼繪製的」
在整個 activity 的生命週期中,setContentView 是在 onCreate 中調用的,它實現了對資源文件的解析,完成了 xml 文件到 View 的轉化。那麼 View 真正開始繪製是在哪一個生命週期呢?
答案是 onResume 結束後
他們的關係在源碼中一目瞭然。
從源碼中能夠看到,onResume 以後,ActivityThread 經過調用 activity 中 windowmanager 的 addView 方法,將 decorView 傳入到 ViewRootImpl 的 setView 方法中,經過 setView 來完成 View 的繪製。
問題又來了,setView 到底有什麼魔法,爲何他就能完成 View 的繪製工做呢?
咱們再來看一下 setView 方法
簡單來講 setView 作了三件事
① 檢查繪製的線程是否是建立 View 的線程。這裏能夠引伸出一個問題,View 的繪製必須在主線程嗎?
② 經過同步屏障保證繪製 View 的任務是最優先的
③ 調用 performTraversals 完成 measure,layout,draw 的繪製
看到這裏,ViewRootImpl 的繪製基本就完成了。其實這也是面試官但願聽到的內容。考察的是面試者對 View 繪製體系的理解。
後續 ViewGroup 和 View 的繪製實際上是 performTraversals 對整個 ViewTree 的繪製。他們的關係能夠用下面這張圖表示
「不錯不錯,看來你對 Viewrootimpl 的繪製過程掌握的不錯嘛,你剛纔提到 View 的繪製是在 onResume 以後纔開始的,那爲何我在 onCreate 中調用 View.post 方法能夠獲得 View 的寬高呢」
這個問題乍看挺唬人的。其實看一眼源碼大概就明白了
View.post 會判斷當前 View 是否已經被添加到 window 上。若是添加了則當即執行 runnable,若是沒有被添加則先放到一個隊列中存儲起來,等添加到 window 上時再執行。
而 View 被測量完成後纔會 attachToWindow。因此當 post 的 runnable 執行時,View 已經繪製完成了。
「能夠能夠。看來這個小細節你注意到了。再問你個簡單的問題,你剛纔說到 measure 方法吧,那你說說什麼是 MeasureSpec?爲何測量寬高要用它做爲參數呢?」
這個問題看似很簡單死板,實際上是想考察對 View 測量的理解。
View 的大小不只僅取決於自身的寬高,還取決於父 View 的大小和測量模式。一個 200200 的父 View 是不可能容納一個 300300 的子 View 的,父 View 的 wrap_content 和 match_content 也會影響子 View 的大小。
因此 View 的 measure 函數其實應該有 4 個參數:父 View 的寬,父 View 的高,寬的測量模式,高的測量模式。
Android 這裏用了一個巧妙的設計,用一個 Int 值來表示寬/高的測量模式和大小。一個 int 有 32 位,前 2 位表示測量 MODE,後 30 位表示 SIZE。
爲何要用 2 位表示 MODE 呢?由於 MODE 只有 3 種呀,UNSPECIFIED,EXACTLY,AT_MOST,小傻瓜。
「不錯啊小夥子,那我自定義一個 View 的時候,若是不對 MeasureSpec 作處理。使用這個 View 時寬高傳入 wrap_content,結果會怎麼樣?」
這個考察的就是 View 繪製的實際運用了。當咱們自定義一個 View 時,若是繼承的是 View,measure 方法走的就是 View 默認的邏輯
因此當咱們自定義 View 時,若是沒有對 MODE 作處理,設置 wrap_content 和 match_content 結果實際上是同樣的,View 的寬高都是取父 View 的寬高。
「呦呵,那你說說 invaliate 和 requestlayout 方法的區別」
前面咱們說到,ViewRootImpl 做爲頂級 View 負責 View 的繪製。因此簡單來講,requestlayout 和 invaliate 最終都會向上回溯調用到 ViewRootImpl 的 postTranversals 方法來繪製 View。
不一樣的是 requestlayout 會繪製 View 的 measure,layout 和 draw 過程。invaliate 由於只添加了繪製 draw 的標誌位,只會繪製 draw 過程。
「能夠能夠,看來 View 繪製這塊你理解的不錯嘛。來考你個小算法,實現一下 findViewbyid 的過程」
通常對開發而言,算法的考察都不會太深,主要是常見算法的簡單使用。目的是對業務中遇到的一些問題有更好的解決思路。像這個問題實際上是想考察一下遞歸算法的簡單使用。
「小夥子準備的不錯嘛,好了,View 繪製這塊我沒有什麼問題了,咱們來聊聊 View 事件處理吧....」
View 繪製相關的問題到這裏就結束啦。若是你們以爲還不錯的話,歡迎各位點贊,收藏,關注三連~
後續我還會繼續更新【面試官爸爸】這個系列,包括事件處理,Handler,Activity 啓動流程,編譯打包優化,Context 等面試最常問的問題。若是不想錯過,歡迎點贊,收藏,關注我!
也能夠關注個人公衆號 @方木Rudy 裏面不只有技術,還有故事和感悟。你的支持,是我不斷創做的動力!
哦對了,是否是看完一遍以爲不夠爽?雜七雜八說一大堆複習的時候一點也不輕鬆! 嘿嘿,我把上面提到的全部問題整理成了思惟導圖,方便各位觀衆老爺複習 ~
我是方木
一個在互聯網世界掙扎向上的打工人
努力生活,努力向前
歡迎來公衆號找我玩~ @方木Rudy