🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## 什么是 ViewRootImpl 相比 Viewgroup 和 View,ViewRootImpl 可能更为陌生,实际开发中我们基本用不到它。那么 > 什么是 ViewRootImpl 呢? 从结构上来看,ViewRootImpl 和 ViewGroup 其实是一种东西 ![](https://img.kancloud.cn/87/5c/875c1faf3ef7ea479e1d969e2ffe6272_1500x400.png) 它们都继承了 ViewParent。ViewParent 是一个接口,定义了一些父 View 的基本行为,比如 requestlayout,getparent 等。不同的是,ViewRootImpl 并不会像 ViewGroup 一样被真正绘制在屏幕上。在 activity 中,它是专门用来绘制 DecorView 的,核心方法是 setView ## “activity,window,View 三者之间的关系是什么?” 我们可以通过一张图来说明。 ![](https://img.kancloud.cn/89/25/8925c9c22b24f3461dce89cace67b68c_612x810.png) 如图所示,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 真正开始绘制 他们的关系在源码中一目了然。 ![](https://img.kancloud.cn/0d/37/0d37f65f3893b3ed8826bf588fed8ab2_1702x2710.png) 从源码中可以看到,onResume 之后,ActivityThread 通过调用 activity 中 windowmanager 的 addView 方法,将 decorView 传入到 ViewRootImpl 的 setView 方法中,通过 setView 来完成 View 的绘制。 问题又来了,setView 到底有什么魔法,为什么他就能完成 View 的绘制工作呢? ## ViewRootImpl 是如何绘制 View 的 我们再来看一下 setView 方法 ![](https://img.kancloud.cn/31/f2/31f20eb0aaae8d0386e1d12a0f36524b_1584x2248.png) 简单来说 setView 做了三件事 ① 检查绘制的线程是不是创建 View 的线程。这里可以引申出一个问题,View 的绘制必须在主线程吗? ② 通过同步屏障保证绘制 View 的任务是最优先的 ③ 调用 performTraversals 完成 measure,layout,draw 的绘制 看到这里,ViewRootImpl 的绘制基本就完成了。其实这也是面试官希望听到的内容。考察的是面试者对 View 绘制体系的理解。 后续 ViewGroup 和 View 的绘制其实是 performTraversals 对整个 ViewTree 的绘制。他们的关系可以用下面这张图表示 ![](https://img.kancloud.cn/c1/1b/c11b40772b9d0b41b5f2d2e3235cabed_1408x940.png) ### 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 的宽高呢” 这个问题乍看挺唬人的。其实看一眼源码大概就明白了 ![](https://img.kancloud.cn/8c/40/8c403e5c38cf6cb52f0957a99ef6da07_1020x610.png) 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 默认的逻辑 ![](https://img.kancloud.cn/c7/c9/c7c955b7ce49a425a593f797cd3e4586_1584x1156.png) 所以当我们自定义 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)