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 ...
随机推荐
- DNS无法区域传送(axfr,ixfr)
这两天博主在学习dns服务器的配 首先简单介绍一下axfr,ixfr axfr:完全区域传送 ixfr :增量区域传送 主要是在dns主从服务器上面进行备份更新的. ----------------- ...
- C# 禁止在textBox输入框输入非法字符
首先添加下面代码 //禁止在textBox输入框输入非法字符 private void keypressed(Object o, KeyPressEventArgs e) { if (e.KeyCha ...
- [Bzoj3991]寻宝游戏(dfs序+set)
Description 题目链接 Solution 用set按dfs序维护当前的宝物序列,那么答案为相邻2个点的距离加上头尾2个的距离 Code #include <cstdio> #in ...
- 邮件系统之Postfix与Dovecot
电子邮件系统 电子邮件系统基于邮件协议来完成电子邮件的传输,常见的邮件协议有: 简单邮件传输协议(Simple Mail Transfer Protocol,SMTP):用于发送和中转发出的电子邮件, ...
- 关于DIV内文字垂直居中的写法
最近在写UI,或多或少用到了CSS,在这记录一下,今天用到的DIV内文字垂直居中的写法, 因为所做的项目都是基于WebKit内核浏览器演示的,所以我们今天采用的是-webkit-box的写法: dis ...
- Android Studio自定义模板代码
http://blog.csdn.net/h183288132/article/details/51916399 生成模板看上面这个博客就可以了,不再重复制造轮子. 不过需要补充的是: 还应该有下面的 ...
- CodeIgniter学习笔记四:CI中的URL相关函数,路由,伪静态,去掉index.php
一.URL相关函数 1.加载url模块 加载url有两种方式: a.自动加载:在 application/config/autoload.php 中开启 $autoload['helper'] = a ...
- 【Deep Learning】林轩田机器学习技法
这节课的题目是Deep learning,个人以为说的跟Deep learning比较浅,跟autoencoder和PCA这块内容比较紧密. 林介绍了deep learning近年来受到了很大的关注: ...
- 架构师速成5.1-小学gtd进阶 分类: 架构师速成 2015-06-26 21:17 313人阅读 评论(0) 收藏
人生没有理想,那和咸鱼有什么区别. 有了理想如何去实现,这就是gtd需要解决的问题.简单说一下gtd怎么做? 确定你的目标,如果不能确定长期目标,至少需要一个2年到3年的目标. 目标必须是可以衡量的, ...
- Python全栈工程师 (exercises)
# 1:给定一个数,判断他是正数,负数,还是0 a = int(input("请输入一该个整数")) if a == 0: print(a, "是0") eli ...