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 ...
随机推荐
- Redis 数据类型List链表
list类型是一个双向链表. 上进上出:栈 例1 lpush newlogin tom lpush newlogin jim lpush newlogin php lpush newlogin th ...
- P2158 [SDOI2008]仪仗队 欧拉函数模板
题目描述 作为体育委员,C君负责这次运动会仪仗队的训练.仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图 ...
- TouTiao开源项目 分析笔记2
1.Constant常量定义类 1.1.源代码 public class Constant { public static final String USER_AGENT_MOBILE = " ...
- Android 布局开发之百分比布局、弹性布局
1.百分比布局 很简单,超级简单.引用之后就可以使用了. compile 'com.android.support:percent:23+' git地址: https://github.com/Jul ...
- Java中的垃圾回收机制&内存管理&内存泄漏
1. Java在创建对象时,会自动分配内存,并当该对象引用不存在的时候,释放这块内存. 为什么呢? 因为Java中使用被称为垃圾收集器的技术来监视Java程序的运行,当对象不再使用时,就自动释放对象所 ...
- string函数Contains()实例
public bool Contains(string value)如果值参数出现在此字符串内,或者值为空字符串(“”),则为true; 否则为false using System; class Ex ...
- 《Cracking the Coding Interview》——第7章:数学和概率论——题目3
2014-03-20 02:05 题目:给定笛卡尔二维平面上两条直线,判断它们是否相交. 解法:相交.重合.平行. 代码: // 7.3 Given two lines on the Cartesia ...
- Djano之写api使用django_rest_framework【海瑞博客】
使用django rest framework 可以更快速和友好的编写api,当然网上有很多教程,对于高手来说相对很简单,对于新手来说,根本搞不明白.那是你没有搞明白你自己的职责,做为后端,我们只要提 ...
- 5.修改haproxy配置文件
需求: 1.查 输入:www.oldboy.org 获取当前backend下的所有记录 2.新建 输入: arg = { 'backend': 'www.oldboy.org', 'record':{ ...
- Python——数据类型之list、tuple
本篇主要内容 • list初识 • list元素的访问 • list内部所有的方法 • tuple介绍和与list用法的比较 我觉得Python里面用的最多的就是List了,感觉好强大.他能存 ...