Android-面试官:简述一下-View-的绘制流程,这个都答不出来还想进大厂?
总结
现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。
我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。
Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。
如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。
(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
//将xml布局传递到Window当中
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
从代码可以看出,Activity的setContentView实质是将View传递到Window的setContentView()方法中,Window的setContenView会在内部调用installDecor()方法创建DecorView,看一下它的部分源码:
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//初始化DecorView以及其内部的content
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
…
} else {
//将contentView加载到DecorVoew当中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
…
}
private void installDecor() {
…
if (mDecor == null) {
//实例化DecorView
mDecor = generateDecor(-1);
…
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//获取Content
mContentParent = generateLayout(mDecor);
}
…
}
protected DecorView generateDecor(int featureId) {
…
return new DecorView(context, featureId, this, getAttributes());
}
通过generateDecor()new一个DecorView,然后调用generateLayout()获取DecorView中content,最终通过inflate将Activity视图添加到DecorView中的content中,但此时DecorView还未被添加到Window中。添加操作需要借助ViewRootImpl。
ViewRootImpl的作用是用来衔接WindowManager和DecorView,在Activity被创建后会通过WindowManager将DecorView添加到PhoneWindow中并且创建ViewRootImpl实例,随后将DecorView与ViewRootImpl进行关联,最终通过执行ViewRootImpl的performTraversals()开启整个View树的绘制。
关于Activity在何时将DecorView添加到Window以及何时创建 ViewRootImpl,这块内容牵扯面比较广,涉及到Activity启动流程、ActivityManagerService(AMS)、WindowManagerService(WMS),内容太过于深入加上作者能力有限就不误人子弟了。如有兴趣推荐查阅刘皇叔《Android进阶解密》,书中对这方面内容讲解还是比较全面的 。
2. 绘制过程
从第一小节可知,View的绘制是从ViewRootImpl的performTraversals()方法开始,从最顶层的View(ViewGroup)开始逐层对每个View进行绘制操作,下面来看一下该方法部分源代码:
private void performTraversals() {
…
//measur过程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
…
//layout过程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
…
//draw过程
performDraw();
}
这方法大概有几百行,机智的作者抽出三句精华呈现给大家~~~
- measure:为测量宽高过程,如果是ViewGroup还要在onMeasure中对所有子View进行measure操作。
- layout:用于摆放View在ViewGroup中的位置,如果是ViewGroup要在onLayout方法中对所有子View进行layout操作。
- draw:往View上绘制图像。
示意图如下: 确实不想画图了,从刚哥的书里拍一张吧~~~

2.1 Measure
performMeasure()源码
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
可以看出从mView(最顶层ViewGroup)开始进行测量操作,然后逐层遍历View并执行measure操作。
MeasureSpac
Measure是View绘制三个过程中的第一步,提到Measure就不得不提MeasureSpac它是一个32位int类型数值,高两位SpacMode代表测量模式,低30位SpacSize代表测量尺寸,是View的内部类,源码如下:
public class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
}
内部也包含三种测量模式:
- **UNSPECIFIED :**父布局不会对子View做任何限制,例如我们常用的
ScrollView就是这种测量模式。 - **EXACTLY :**精确数值,比如使用了
match_parent或者xxxdp,表示父布局已经决定了子View的大小,通常在这种情况下View的尺寸就是SpacSize - **AT_MOST :**自适应,对应
wrap_content子View可以根据内容设置自己的大小,但前提是不能超出父ViewGroup的宽高。
注意点:
在我们自定义View的过程中都会在onMeasure中进行宽高的测量,这个方法会从父布局中接收两个参数
widthMeasureSpac和heightMeasureSpac,所以子布局的宽高大小需要受限于父布局。
在自定义View宽高测量的过程中,我们需要获取MeasurSpac中的宽高和测量模式,自定义ViewGroup也必须给子View传递MeasurSpac,Android也给我们提供了计算MeasurSpac 和通过MeasurSpac 获取相应值的方式,都位于MeasurSpac中,具体代码如下:
public static class MeasureSpec {
public static int makeMeasureSpec( int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK)
}
}
从ViewGroup到View对尺寸和模式进行了一次封装和拆解,其目的是为了减少对象的创建,避免造成不必要的内存浪费。
LayoutParams
在刚接触Android的时候经常有一个疑问,为什么View设置自己的宽高,还要创建一个xxx.LayoutParams?前面也提到了,子View的宽高是要受限于父布局的,所以不能通过setWidth或者setHeight直接设置宽高的,另外 LayoutParams的作用不仅如此,比如一个View的父布局是RelativeLayout,可以通过设置RelativeLayout.LayoutParams的above,below等属性来调整在父布局中的位置。
自定义View宽高测量演示
创建一个类继承View,重写其onMeasure()方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//默认宽
int defaultWidth = 0;
//默认高
int defaultHeight = 0;
setMeasuredDimension(
getDefaultSize(defaultWidth, widthMeasureSpec),
getDefaultSize(defaultHeight, heightMeasureSpec));
}
一般的自定义View中,如果对宽高没有特殊需求可直接通过getDefaultSize()方法获取,该方法位于View中源码如下:
public static int getDefaultSize(int size, int measureSpec) {
//默认尺寸
int result = size;
//获取测量模式
int specMode = MeasureSpec.getMode(measureSpec);
//获取尺寸
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
从代码分析可知,获取mode和size后会分别对三种测量模式进行判断,UNSPECIFIED使用默认尺寸,而AT_MOST和EXACTLY使用父布局给出的测量尺寸。尺寸计算完毕后通过setMeasuredDimension(width,height)设置最终宽高。
2.2 Layout
performLayout()部分源码:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
…
final View host = mView;
if (host == null) {
return;
}
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
…
}
最后
在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
3945851)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
更多推荐

所有评论(0)