View的三次measure,两次layout和一次draw
我在《Android视图结构》这篇文章中已经描述了Activity
,Window
和View
在视图架构方面的关系。前天,我突然想到为什么在setContentView
中能够调用findViewById
函数?View
那时不是还没有被加载,测量,布局和绘制啊。然后就搜索了相关的条目,发现findViewById
只需要在inflate
结束之后就可以。于是,我整理了Activity生命周期和View的生命周期的关系,并再次做一下总结。
为了节约你的时间,本篇文章的主要内容为:
- Activity的生命周期和它包含的View的生命周期的关系
- Activity初始化时View为什么会三次measure,两次layout但只一次draw?
- ViewRoot的初始化过程
Activity的生命周期和View的生命周期
我通过一个简单的demo来验证Activity
生命周期方法和View
的生命周期方法的调用先后顺序。请看如下截图
在onCreate
函数中,我们通常都调用setContentView
来设置布局文件,此时Android系统就会读取布局文件,但是视图此时并没有加载到Window
上,并且也没有进入自己的生命周期。
只有等到Activity
进入resume状态时,它所拥有的View
才会加载到Window
上,并进行测量,布局和绘制。所以我们会发现相关函数的调用顺序是:
- onResume(Activity)
- onPostResume(Activity)
- onAttachedToWindow(View)
- onMeasure(View)
- onMeasure(View)
- onLayout(View)
- onSizeChanged(View)
- onMeasure(View)
- onLayout(View)
onDraw(View)
大家会发现,为什么
onMeasure
先调用了两次,然后再调用onLayout
函数,最后还有在依次调用onMeasure
,onLayout
和onDraw
函数呢?
ViewGroup的measure
大家应该都知道,有些ViewGroup
可能会让自己的子视图测量两次。比如说,父视图先让每个子视图自己测量,使用View.MeasureSpec.UNSPECIFIED
,然后在根据每个子视图希望得到的大小不超过父视图的一些限制,就让子视图得到自己希望的大小,否则就用其他尺寸来重新测量子视图。这一类的视图有FrameLayout
,RelativeLayout
等。
在《Android视图结构》中,我们已经知道Android视图树的根节点是DecorView
,而它是FrameLayout
的子类,所以就会让其子视图绘制两次,所以onMeasure
函数会先被调用两次。
// FrameLayout的onMeasure函数,DecorView的onMeasure会调用这个函数。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
.....
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
}
}
........
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
........
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
你以为到了这里就能解释通View初始化时的三次measure,两次layout却只一次draw吗?那你就太天真了!我们都知道,视图结构中不仅仅是DecorView
是FrameLayout
,还有其他的需要两次measure子视图的ViewGroup
,如果每次都导致子视图两次measure,那效率就太低了。所以View
的measure
函数中有相关的机制来防止这种情况。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
// 当FLAG_FORCE_LAYOUT位为1时,就是当前视图请求一次布局操作
//或者当前当前widthSpec和heightSpec不等于上次调用时传入的参数的时候
//才进行从新绘制。
if (forceLayout || !matchingSize &&
(widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec)) {
......
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
......
}
源码看到这里,我几乎失望的眼泪掉下来!没办法,只能再想其他的方法来分析这个问题。
断点调试大法好
为了分析函数调用的层级关系,我想到了断点调试法。于是,我果断在onMeasure
和onLayout
函数中设置了断点,然后进行调试。
在《Android视图架构》一文中,我们知道ViewRoot
是DecorView
的父视图,虽然它自己并不是一个View
,但是它实现了ViewParent
的接口,Android正是通过它来实现整个视图系统的初始化工作。而它的performTraversals
函数就是视图初始化的关键函数。
对比两次onMeasure
被调用时的函数调用帧,我们可以轻易发现ViewRootImpl
的performTraversals
函数中直接和间接的调用了两次performMeasure
函数,从而导致了View
最开始的两次measure过程。
然后在相同的performTraversals
函数中会调用performLayout
函数,从而导致View
进行一轮layout过程。
但是为什么这次performTraversals
并没有触发View
的draw过程呢?反而是View
又将重新进行一轮measure,layout过程之后才进行draw。
两次performTraversals
通过断点调试,我们发现在View
初始化的过程中,系统调用了两次performTraversals
函数,第一次performTraversals
函数导致了View的前两次的onMeasure
函数调用和第一次的onLayout
函数调用。后一次的performTraversals
函数导致了最后的onMeasure
,onLayout
,和onDraw
函数的调用。但是,第二次performTraversals
为什么会被触发呢?我们研究一下其源码就可知道。
private void performTraversals() {
......
boolean newSurface = false;
//TODO:决定是否让newSurface为true,导致后边是否让performDraw无法被调用,而是重新scheduleTraversals
if (!hadSurface) {
if (mSurface.isValid()) {
// If we are creating a new surface, then we need to
// completely redraw it. Also, when we get to the
// point of drawing it we will hold off and schedule
// a new traversal instead. This is so we can tell the
// window manager about all of the windows being displayed
// before actually drawing them, so it can display then
// all at once.
newSurface = true;
.....
}
}
......
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
......
performDraw();
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
......
}
由源代码可以看出,当newSurface
为真时,performTraversals
函数并不会调用performDraw
函数,而是调用scheduleTraversals
函数,从而再次调用一次performTraversals
函数,从而再次进行一次测量,布局和绘制过程。
我们由断点调试可以轻易看到,第一次performTraversals
时的newSurface
为真,而第二次时是假。
View的三次measure,两次layout和一次draw的更多相关文章
- Activtiy完全解析(三、View的显示过程measure、layout、draw)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/52840065 本文出自:[openXu的博客] 在Activity完全解析的第一篇文章A ...
- Android应用层View绘制流程之measure,layout,draw三步曲
概述 上一篇博文对DecorView和ViewRootImpl的关系进行了剖析,这篇文章主要是来剖析View绘制的三个基本流程:measure,layout,draw.仅仅有把这三个基本流程搞清楚了, ...
- View (三) 视图绘制流程完全解析
相 信每个Android程序员都知道,我们每天的开发工作当中都在不停地跟View打交道,Android中的任何一个布局.任何一个控件其实都是直接或间 接继承自View的,如TextView.Butto ...
- View学习(二)-View的测量(measure)过程
在上一篇文章中,我们介绍了DecorView与MeasureSpec, 下面的文章就开始讨论View的三大流程. View的三大流程都是通过ViewRoot来完成的.ViewRoot对应于ViewRo ...
- JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(三):两个Viewmodel搞定增删改查
前言:之前博主分享过knockoutJS和BootstrapTable的一些基础用法,都是写基础应用,根本谈不上封装,仅仅是避免了html控件的取值和赋值,远远没有将MVVM的精妙展现出来.最近项目打 ...
- Android自定义View(三、深入解析控件测量onMeasure)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 ...
- Android自定义View前传-View的三大流程-Measure
Android自定义View前传-View的三大流程-Measure 参考 <Android开发艺术探索> https://developer.android.google.cn/refe ...
- 【朝花夕拾】Android自定义View篇之(四)自定义View的三种实现方式及自定义属性使用介绍
前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10979161.html],谢谢! 尽管Android系统提供了不少控件,但是有很多酷炫效果仍然 ...
- 高级UI晋升之常用View(三)上篇
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将先从以下两个内容来介绍常用View: [RecycleView] [Ca ...
随机推荐
- 【PHP项目】伪静态规则
伪静态规则写法RewriteRule-htaccess详细语法使用 2016年03月30日 16:53:59 阅读数:20340 伪静态实际上是利用php把当前地址解析成另一种方法来访问网站,要学伪静 ...
- Android内购订单验证 --- php实现
直接上代码: function googleVerify($sdata,$google_public_key) { $sdata = json_decode($sdata,true); $in_app ...
- iOS各个版本的特点
一.导航控制器中: iOS7: 栈顶控制器默认是320*480:控制器有64的高度看不见:
- 裸机——iNand
1.先晓得iNand的基础知识 iNand是在SD卡基础上发展来的,而SD卡是在MMC的基础上发展来的,MMC是在Nand的基础上发展来的 我们晓得Nand的基础知识,而MMC对Nand大致做了两个改 ...
- Codeforces146D 概率DP
Bag of mice The dragon and the princess are arguing about what to do on the New Year's Eve. The drag ...
- redhat 配置本地yum源
redhat配置3个源就够了: 1.本地yum源,就是你本地的ISO 2.配置163源 3.配置epel源 环境:redhat7 + vmw 12 pro 1.配置本地yum源 要配置本地源,需要先把 ...
- centos安装xfce及输入法
一.执行CentOS7 最小安装 去官网 https://www.centos.org/ 下载CentOS-7-x86_64-Minimal-1804.iso,然后使用rufus刻录U盘,安装之.安装 ...
- struts2官方 中文教程 系列三:使用struts2 标签 tag
避免被爬,先贴上本帖地址:struts2 官方系列教程一:使用struts2 标签 tag http://www.cnblogs.com/linghaoxinpian/p/6901316.html 本 ...
- NetCore log4net 集成以及配置日志信息不重复显示或者记录
NetCore log4net 集成,这是一个很常见而且网上大批大批的博文了,我写这个博文主要是为了记录我在使用过程中的一点小收获,以前在使用的过程中一直没有注意但是其实网上说的不清不楚的问题. 官方 ...
- HNOI2018 摸鱼记
HNOI2018 摸鱼记 今天我又来记流水账啦 Day 0 颓废的一天. 我,球爷和杜教在颓膜膜.io ych看起来在搓碧蓝 鬼知道哥达鸭干了什么 学习氛围只局限在机房的一角 后来全体Oier开会,5 ...