迎面走来的一位中年男子,他一手拿着保温杯,一手抱着笔记本电脑,顶着惺忪的睡眼,不紧不慢地走着,很少的几根头发在他头顶自由飞翔。过了一会,他面对着我坐下,放下电脑和保温杯,边揉眉头边对我说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