摘要:前天,我突然想到为什么在中能够调用函数那时不是还没有被加载,测量,布局和绘制啊。然后在相同的函数中会调用函数,从而导致进行一轮过程。
我在《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。
通过断点调试,我们发现在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的相关内容,我之后要继续学习相关内容!
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/13309.html
摘要:这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。从顶层父到子递归调用方法,方法又回调。 目录介绍 3.0.0.1 View的绘制需要经过哪些过程?有哪些常用回调方法?View的绘制流程的详细流程是怎样的? 3.0.0.2 View绘制流程,当一个TextView的实例调用setText()方法后执行了什么?请说一下原理…… 3.0.0.3 requestLayout()、...
摘要:它所做的就是对需要的视图进行测量视图大小确定视图的位置与绘制视图。自定义时不需要去管理该方法。自定义时一般都要自定义属性,所以都会在中定义与最后在自定义中通过获取。 前言 Android自定义View的详细步骤是我们每一个Android开发人员都必须掌握的技能,因为在开发中总会遇到自定义View的需求。为了提高自己的技术水平,自己就系统的去研究了一下,在这里写下一点心得,有不足之处希望...
摘要:因为是在线程的,所以方法的作用就是将非线程的刷新操作切换到线程,以便在线程中调用方法刷新。一句话总结方法的作用就是实现了消息机制,可以使我们在非线程也能调用刷新的方法。 目录介绍 01.invalidate,requestLayout,postInvalidate区别 02.invalidate深入分析 03.postInvalidate深入分析 04.requestLayout深入...
摘要:的绘图性能一直完爆的其中一个原因就是因为它简单的布局系统不会因为布局的复杂性增强而增加计算量。当然学习建议还是多看官方文档 回顾 Hello,通过Android程序员 如何入门iOS ——故事从这里开始 作为一个Androider 去看iOS程序的目录结构应该算有个大概的理解了,接下去我们小小介绍下和我们交道打的最多的UIViewController。 什么是ViewControlle...
摘要:的绘图性能一直完爆的其中一个原因就是因为它简单的布局系统不会因为布局的复杂性增强而增加计算量。当然学习建议还是多看官方文档 回顾 Hello,通过Android程序员 如何入门iOS ——故事从这里开始 作为一个Androider 去看iOS程序的目录结构应该算有个大概的理解了,接下去我们小小介绍下和我们交道打的最多的UIViewController。 什么是ViewControlle...
阅读 2338·2023-04-25 18:13
阅读 531·2021-11-22 12:10
阅读 2789·2021-11-22 11:57
阅读 1988·2021-11-19 11:26
阅读 1879·2021-09-22 15:40
阅读 1282·2021-09-03 10:28
阅读 2529·2019-08-30 15:53
阅读 1767·2019-08-30 15:44