我在《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一次的原因,但是Android系统设计时,为什么要将整个初始化过程设计成这样?我却还没有明白,为什么当Surface为新的时候,要推迟绘制,重新进行一轮初始化,这些可能都要涉及到Surface的相关内容,我之后要继续学习相关内容!

android 二次绘制 layout,View的三次measure,两次layout和一次draw相关推荐

  1. Android UI绘制流程分析(三)measure

    源码版本Android 6.0 请参阅:http://androidxref.com/6.0.1_r10 本文目的是分析从Activity启动到走完绘制流程并显示在界面上的过程,在源码展示阶段为了使跟 ...

  2. Android(Xamarin)之旅(三)

    Android(Xamarin)之旅(三) 原文:Android(Xamarin)之旅(三) 前面两篇说到了Xamarin的安装和一些简单的控件,今天来说说一些对话框和提示信息,以及简单的布局元素. ...

  3. Android二维码扫描开发(一):实现思路与原理

    2019独角兽企业重金招聘Python工程师标准>>> Android二维码扫描开发(一):实现思路与原理 Android二维码扫描开发(二):YUV图像格式详解 Android二维 ...

  4. Android面试收集录12 View测量、布局及绘制原理

    一.View绘制的流程框架 View的绘制是从上往下一层层迭代下来的.DecorView-->ViewGroup(--->ViewGroup)-->View ,按照这个流程从上往下, ...

  5. 自定义View:测量measure,布局layout,绘制draw

    1. 什么是View 在Android的官方文档中是这样描述的:表示了用户界面的基本构建模块.一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理. 手机屏幕上所有看得见摸得着的都是Vie ...

  6. Android 系统(230)---View 绘制流程 —— 基础(1)

    View 绘制流程 -- 基础(1) View 绘制流程 -- 基础 1. View分类 类别 解释 特点 单一视图 即一个View,如 TextView 不包含子View 视图组 即多个View组成 ...

  7. Android 仿PhotoShop调色板应用(二) 透明度绘制之AlphaPatternDrawable

    版权声明:本文为博主原创文章,未经博主允许不得转载. Android 仿PhotoShop调色板应用(二) 透明度绘制之AlphaPatternDrawable 这里讲一下如何实现PS调色板中的透明度 ...

  8. HenCoder Android 开发进阶:自定义 View 1-5 绘制顺序

    这期是 HenCoder 自定义绘制的第 1-5 期:绘制顺序 之前的内容在这里:  HenCoder Android 开发进阶 自定义 View 1-1 绘制基础  HenCoder Android ...

  9. R语言plotly可视化:使用PCA算法进行数据降维、使用plotly可视化PCA所有的主成分绘制散点图矩阵、降维后的两个(三个)核心主成分的二维、三维可视化图形、方差解释的量、载荷图等

    R语言plotly可视化:使用PCA算法进行数据降维.使用plotly可视化PCA所有的主成分绘制散点图矩阵.降维后的两个(三个)核心主成分的二维.三维可视化图形.方差解释的量.载荷图等 目录

最新文章

  1. 廖雪峰 练习 把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字...
  2. java的equals方法_Java Date equals()方法与示例
  3. mysql master or master copy
  4. 阶段5 3.微服务项目【学成在线】_day01 搭建环境 CMS服务端开发_12-MongoDb入门-基础概念...
  5. Saltstack 安装应用笔记一
  6. html实验原理及目的,网页设计实验报告_图文
  7. idea常用图标总结
  8. Ardunio开发实例-WS2812B独立寻址LED调色调光
  9. 【华为机试题 HJ22】汽水瓶
  10. php mysql计数器代码一例
  11. 计算机主板设置语言,技嘉主板bios设置中文对照的方法步骤
  12. 根据市场需求和反馈调整产品定位,调整产品既定设计策略和营销策略
  13. AGV搬运机器人磁导航传感器D-MNSV7-X16安装方法与注意事项
  14. hive的分区和分桶
  15. C++学习——布尔型,操作符别名,函数和引用
  16. 关于php中laravel框架的学习--适合菜鸟初学者
  17. DXVA 处理交错视频
  18. AWS、Google、Apple云端宕机背后的故事
  19. iqoo一代充电测试软件,iqoo7充电速度测试(iqoo7充电测试)
  20. Tomcat 8005/tcp端口安全配置

热门文章

  1. android 蒙层广告1,subnvue安卓机打开只显示蒙层,没有任何内容【报Bug】
  2. python下载之后无法启动_安装后启动时,适用于Python的Eric IDE崩溃
  3. php使用邮件找回密码,php利用Zend_Mail发送邮件(实现邮件重设密码功能)
  4. pandas处理csv
  5. 外点惩处函数法·约束优化问题
  6. module.js:549 throw err;
  7. 2018软工实践第六次作业-团队选题报告
  8. iterator and iterable
  9. JMS--Queue实战
  10. python使用telnet远程连接linux系统读取信息_Linux服务笔记之一:Telnet 远程登录