Android 面试官:“来给我讲讲 View 绘制?
迎面走来的一位中年男子,他一手拿着保温杯,一手抱着笔记本电脑,顶着惺忪的睡眼,不紧不慢地走着,不多的几根头发在他头顶自由飞翔。过了一会,他面对着我坐下,放下电脑和保温杯,边揉眉头边对我说
“来面试的?”
“对对对” 我赶紧答应
“行吧,那你讲讲 View 的绘制流程吧”
View 的绘制流程应该是每个初高级 Android 攻城狮必知必会的东西,也是面试必考的内容,每个人都有不同的回答方式。
简单点譬如 measure,layout,draw 分别对应测量,布局,绘制三个过程,高明一点的会引申出 Handler,同步屏障,View 的事件传递,甚至 activity 的启动过程。掌握哪些东西,如何回答,能够给面试官一种清晰,了然于胸的感觉,同时又不会被追问三连一问三不知。各位老爷听我慢慢道来。
“噢噢,View 的绘制啊。这个可以分为顶级 View 的绘制,Viewgroup 的绘制和 View 的绘制三个方面。顶级 View 就是 ViewrootImpl”
将回答的内容分类是体现自己思考能力和知识结构的重要表现。
相比 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 的绘制。他们的关系可以用下面这张图表示
“不错不错,看来你对 Viewrooti
mpl 的绘制过程掌握的不错嘛,你刚才提到 View 的绘制是在 onResume 之后才开始的,那为什么我在 onCreate 中调用 View.post 方法可以得到 View 的宽高呢”
这个问题乍看挺唬人的。其实看一眼源码大概就明白了
View.post 会判断当前 View 是否已经被添加到 window 上。如果添加了则立即执行 runnable,如果没有被添加则先放到一个队列中存储起来,等添加到 window 上时再执行。
而 View 被测量完成后才会 attachToWindow。所以当 post 的 runnable 执行时,View 已经绘制完成了。
“可以可以。看来这个小细节你注意到了。再问你个简单的问题,你刚才说到 measure 方法吧,那你说说什么是 MeasureSpec?为什么测量宽高要用它作为参数呢?”
评论