[TOC]
## 什么是 ViewRootImpl
相比 Viewgroup 和 View,ViewRootImpl 可能更為陌生,實際開發中我們基本用不到它。那么
> 什么是 ViewRootImpl 呢?
從結構上來看,ViewRootImpl 和 ViewGroup 其實是一種東西

它們都繼承了 ViewParent。ViewParent 是一個接口,定義了一些父 View 的基本行為,比如 requestlayout,getparent 等。不同的是,ViewRootImpl 并不會像 ViewGroup 一樣被真正繪制在屏幕上。在 activity 中,它是專門用來繪制 DecorView 的,核心方法是 setView
## “activity,window,View 三者之間的關系是什么?”
我們可以通過一張圖來說明。

如圖所示,window 是 activity 里的一個實例變量,本質是一個抽象類,唯一的實現類是 PhoneWindow。
activity 的 setContentView 方法實際上是就是交給 phonewindow 去做的。window 和 View 的關系可以類比為**顯示器**和**顯示的內容**。
每個 activity 都有一個“顯示器”**window**,“顯示的內容”就是**DecorView**。這個“顯示器”定義了一些方法來決定如何顯示內容。比如 setTitleColor setTitle 是設置導航欄的顏色和 title , setAllowReturnTransitionOverlap 設置進/出場動畫等等。
所以**window 是 activity 的一個成員變量,window 和 View 是“顯示器”和“顯示內容”的關系。**
這就是他們的關系
## View 是怎么繪制的
### onCreate
在整個 activity 的生命周期中,setContentView 是在 onCreate 中調用的,它實現了對資源文件的解析,完成了 xml 文件到 View 的轉化。期呢?
### onResume 真正開始繪制
他們的關系在源碼中一目了然。

從源碼中可以看到,onResume 之后,ActivityThread 通過調用 activity 中 windowmanager 的 addView 方法,將 decorView 傳入到 ViewRootImpl 的 setView 方法中,通過 setView 來完成 View 的繪制。
問題又來了,setView 到底有什么魔法,為什么他就能完成 View 的繪制工作呢?
## ViewRootImpl 是如何繪制 View 的
我們再來看一下 setView 方法

簡單來說 setView 做了三件事
① 檢查繪制的線程是不是創建 View 的線程。這里可以引申出一個問題,View 的繪制必須在主線程嗎?
② 通過同步屏障保證繪制 View 的任務是最優先的
③ 調用 performTraversals 完成 measure,layout,draw 的繪制
看到這里,ViewRootImpl 的繪制基本就完成了。其實這也是面試官希望聽到的內容。考察的是面試者對 View 繪制體系的理解。
后續 ViewGroup 和 View 的繪制其實是 performTraversals 對整個 ViewTree 的繪制。他們的關系可以用下面這張圖表示

### mChoreographer
—ViewRoot類的requestLayout()方法
—scheduleTraversals()
—mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)請求刷新信號
—mChoreographer內部handler通過msg機制觸發mTraversalRunnable
啟動performTraversals();測繪流程
## 為什么我在 onCreate 中調用 View.post
> “不錯不錯,看來你對 Viewrootimpl 的繪制過程掌握的不錯嘛,你剛才提到 View 的繪制是在 onResume 之后才開始的,那為什么我在 onCreate 中調用 View.post 方法可以得到 View 的寬高呢”
這個問題乍看挺唬人的。其實看一眼源碼大概就明白了

View.post 會判斷當前 View 是否已經被添加到 window 上。如果添加了則立即執行 runnable,如果沒有被添加則先放到一個隊列中存儲起來,等添加到 window 上時再執行。
而 View 被測量完成后才會 attachToWindow。所以當 post 的 runnable 執行時,View 已經繪制完成了。
## MeasureSpec 的理解
View 的大小不僅僅取決于自身的寬高,還取決于父 View 的大小和測量模式。一個 200*200 的父 View 是不可能容納一個 300*300 的子 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
* 精確模式 EXACTLY:父 View 指定了子 View 確切的大小
* 最大模式 AT_MOST:父 View 指定一個大小,子 View 不能超過這個值
* 未指定模式 UNSPECIFIEND: 父 View 不對子 View 有任何限制
### 我自定義一個 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繪制?](https://juejin.cn/post/6979395482946633758)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺