我在《Android视图结构》这篇文章中已经描述了Activity,WindowView在视图架构方面的关系。前天,我突然想到为什么在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,onLayoutonDraw函数呢?

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吗?那你就太天真了!我们都知道,视图结构中不仅仅是DecorViewFrameLayout,还有其他的需要两次measure子视图的ViewGroup,如果每次都导致子视图两次measure,那效率就太低了。所以Viewmeasure函数中有相关的机制来防止这种情况。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
// 当FLAG_FORCE_LAYOUT位为1时,就是当前视图请求一次布局操作
//或者当前当前widthSpec和heightSpec不等于上次调用时传入的参数的时候
//才进行从新绘制。
if (forceLayout || !matchingSize &&
(widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec)) {
......
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
......
}  

源码看到这里,我几乎失望的眼泪掉下来!没办法,只能再想其他的方法来分析这个问题。

断点调试大法好

 为了分析函数调用的层级关系,我想到了断点调试法。于是,我果断在onMeasureonLayout函数中设置了断点,然后进行调试。

 在《Android视图架构》一文中,我们知道ViewRootDecorView的父视图,虽然它自己并不是一个View,但是它实现了ViewParent的接口,Android正是通过它来实现整个视图系统的初始化工作。而它的performTraversals函数就是视图初始化的关键函数。 
 对比两次onMeasure被调用时的函数调用帧,我们可以轻易发现ViewRootImplperformTraversals函数中直接和间接的调用了两次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的更多相关文章

  1. Activtiy完全解析(三、View的显示过程measure、layout、draw)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/52840065 本文出自:[openXu的博客]   在Activity完全解析的第一篇文章A ...

  2. Android应用层View绘制流程之measure,layout,draw三步曲

    概述 上一篇博文对DecorView和ViewRootImpl的关系进行了剖析,这篇文章主要是来剖析View绘制的三个基本流程:measure,layout,draw.仅仅有把这三个基本流程搞清楚了, ...

  3. View (三) 视图绘制流程完全解析

    相 信每个Android程序员都知道,我们每天的开发工作当中都在不停地跟View打交道,Android中的任何一个布局.任何一个控件其实都是直接或间 接继承自View的,如TextView.Butto ...

  4. View学习(二)-View的测量(measure)过程

    在上一篇文章中,我们介绍了DecorView与MeasureSpec, 下面的文章就开始讨论View的三大流程. View的三大流程都是通过ViewRoot来完成的.ViewRoot对应于ViewRo ...

  5. JS组件系列——BootstrapTable+KnockoutJS实现增删改查解决方案(三):两个Viewmodel搞定增删改查

    前言:之前博主分享过knockoutJS和BootstrapTable的一些基础用法,都是写基础应用,根本谈不上封装,仅仅是避免了html控件的取值和赋值,远远没有将MVVM的精妙展现出来.最近项目打 ...

  6. Android自定义View(三、深入解析控件测量onMeasure)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 ...

  7. Android自定义View前传-View的三大流程-Measure

    Android自定义View前传-View的三大流程-Measure 参考 <Android开发艺术探索> https://developer.android.google.cn/refe ...

  8. 【朝花夕拾】Android自定义View篇之(四)自定义View的三种实现方式及自定义属性使用介绍

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10979161.html],谢谢! 尽管Android系统提供了不少控件,但是有很多酷炫效果仍然 ...

  9. 高级UI晋升之常用View(三)上篇

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将先从以下两个内容来介绍常用View: [RecycleView] [Ca ...

随机推荐

  1. Nginx 配置继承模型

    要了解nginx的继承模型,首先需要知道nginx使用多个配置块进行操作.在nginx中,这样的块被称为上下文,例如,放置在服务器上下文中的配置指令驻留在server { }块中,就像放置在http上 ...

  2. day1_作业(账户登录检测)

    #!/usr/local/bin/python3 # -*- coding:utf-8 -*- f=open('/users/zhangyu/PycharmProjects/s14/day1/Home ...

  3. Android面试收集录18 Android Context详解

    Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言 ...

  4. PHP.TP框架下商品项目的优化1-时间插件、鼠标所在行高亮、布局规划页面

    1.优化搜索表单中按时间搜索的功能 添加一个时间插件datetimepicker,在lst.html中,注意要导入jquery.min.js,此处从前文的在线编辑器中导入 <!-- 导入 --& ...

  5. bootstrap设计进度条和圆点

    1.设计进度条.文字前面的圆点和图片 2.思路: (1)设计进度条 (a) 进度条有滚动效果,要加上类.active (b)进度条的颜色通过类.progress-bar-success来写,可以写成. ...

  6. 使用 jQuery Mobile 与 HTML5 开发 Web App 系列文章目录

    使用 jQuery Mobile 与 HTML5 开发 Web App 系列文章目录 时间:2012年9月20日 分类:JavaScript 标签:HTML5‚ jQuery Mobile‚ Web ...

  7. 一道关于C++ 继承/虚函数 笔试题 [转]

    转自:http://www.cnblogs.com/yangyh/archive/2011/06/04/2072393.html 首先这位作者, 因为看了这篇简短的一个博文, 我相同了关于虚函数方面的 ...

  8. oracle 隔离级别、事务怎么开始的以及如何查看数据库采用字符集

    把一下语句全部粘贴至控制台运行后可以查看oracle 隔离级别 declare trans_id ); begin trans_id := dbms_transaction.local_transac ...

  9. ironic state information

    参考: http://blog.csdn.net/zhonglinzhang/article/details/74202562 http://blog.csdn.net/wanghuiict/arti ...

  10. codeblocks17.12 debug 报错:ERROR: You need to specify a debugger program in the debuggers's settings.

    DebugERROR: You need to specify a debugger program in the debuggers's settings.(For MinGW compilers, ...