performTraversals三大流程开始之地

工作一段时间了,但是感觉自己对View的三大流程还不是理解透彻。所以主要根据《Android开发艺术探索》一书和查看源码去了解下View的三大流程。
在《Android开发艺术探索》中说到,ViewRoot是连接WindowManager和DectorView的纽带,View的三大流程都是通过ViewRoot实现的。而ViewRootImpl是ViewRoot的实现。在Activity被创建之后,会将DectorView添加到window上,同时创建ViewRootImpl,并将两者关联起来(通过ViewRootImpl.setView)。
View的绘制流程是从ViewRoot.performTraversals()开始的,并通过调用performMeasure、performLayout、performDraw完成顶级View的三大流程。


《Android开发艺术探索》流程

performTraversals方法很长,分段阅读毕竟容易理解和消化。首先是很长的一段(哈哈),主要作用就是确定当前窗体大小并进行view树的测量(测量会提出来下一段分析)

        // cache mView since it is used so much below...
        final View host = mView;

        if (host == null || !mAdded)
            return;

        //一些窗口变量的处理

        Rect frame = mWinFrame;
        if (mFirst) {//是否第一次请求,构造方法中为true
           ....
           //---Activity当前的Window的宽高的确定---
        } else {
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                //视图大小发生改变,重绘相关标志位置为true
                mFullRedrawNeeded = true;//需要重新绘制标志位
                mLayoutRequested = true; //要求重新Layout标志位
                windowSizeMayChange = true;//Window的尺寸可能改变
            }
        }

        ....

        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
        if (layoutRequested) {
            ...
            //--检测边衬区域是否发生变化,有变化则 insetsChanged 变量置为true
            //测量Window的可能大小,实际上进行了measure()测量过程,只不过这个测量过程不属于三大流程
            // Ask host how big it wants to be
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
        }
        
        ...

        if (layoutRequested) {
            // Clear this now, so that if anything requests a layout in the
            // rest of this function we will catch it and re-run a full
            // layout pass.
            mLayoutRequested = false;
        }
        //前面已经做了一次measure()工作,host宽高和当前窗口宽高不一致则Activity窗口发生变化
        boolean windowShouldResize = layoutRequested && windowSizeMayChange
            && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
                || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.width() < desiredWindowWidth && frame.width() != mWidth)
                || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.height() < desiredWindowHeight && frame.height() != mHeight));
        windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;

        // If the activity was just relaunched, it might have unfrozen the task bounds (while
        // relaunching), so we need to force a call into window manager to pick up the latest
        // bounds.
        windowShouldResize |= mActivityRelaunched;
        //检测相关边衬区域,Activity窗口是否指定了额外的内容区域边衬和可见区域边衬
        // Determine whether to compute insets.
        // If there are no inset listeners remaining then we may still need to compute
        // insets in case the old insets were non-empty and must be reset.
        final boolean computesInternalInsets =
                mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
                || mAttachInfo.mHasNonEmptyGivenInternalInsets;

        ...

        final boolean isViewVisible = viewVisibility == View.VISIBLE;
        final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
        //1.Activity窗口是第一次执行测量、布局和绘制操作,即mFirst == true
        //2.前面windowShouldResize==true,即Activity窗口的大小发生了变化
        //3.前面insetsChanged==true,即Activity窗口的内容区域边衬发生了变化
        //4.viewVisibilityChanged==true,Activity窗口的可见性发生了变化
        //5.变量params指向了一个WindowManager.LayoutParams对象,Activity窗口的属性发生了变化
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;

            if (isViewVisible) {
                // If this window is giving internal insets to the window
                // manager, and it is being added or changing its visibility,
                // then we want to first give the window manager "fake"
                // insets to cause it to effectively ignore the content of
                // the window during layout.  This avoids it briefly causing
                // other windows to resize/move based on the raw frame of the
                // window, waiting until we can finish laying out this window
                // and get back to the window manager with the ultimately
                // computed insets.
                //Activity窗口是否指定了额外的内容区域边衬和可见区域边衬
                insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
            }

            .....

            try {
                ....
                //请求WindowManagerService服务计算Activity窗口的大小以及过扫描区域边衬大小和可见区域边衬大小
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
                .....
                contentInsetsChanged = !mPendingContentInsets.equals(
                        mAttachInfo.mContentInsets);
                ....
                if (contentInsetsChanged) {
                    mAttachInfo.mContentInsets.set(mPendingContentInsets);
                }
                ....
            } catch (RemoteException e) {
            }
            ...
            //计算完毕之后,Activity窗口的大小就会保存在成员变量mWinFrame中
            //变量frame和mWinFrame引用的是同一个Rect对象
            mAttachInfo.mWindowLeft = frame.left;
            mAttachInfo.mWindowTop = frame.top;

            // !!FIXME!! This next section handles the case where we did not get the
            // window size we asked for. We should avoid this by getting a maximum size from
            // the window session beforehand.
            if (mWidth != frame.width() || mHeight != frame.height()) {
                mWidth = frame.width();
                mHeight = frame.height();
            }
            ....
            //-------进行测量过程,下面分析-------
        } else {
            // Not the first pass and no window/insets/visibility change but the window
            // may have moved and we need check that and if so to update the left and right
            // in the attach info. We translate only the window frame since on window move
            // the window manager tells us only for the new frame but the insets are the
            // same and we do not want to translate them more than once.
            //判断window是否有移动,发生移动则执行移动动画
            maybeHandleWindowMove(frame);
        }

上面这一大段代码,主要作用其实就是为了确定Activity窗口的大小,包括内容窗口大小和相关的边衬区域大小的确定。

  • Activity窗口是第一次执行测量、布局和绘制操作,即mFirst == true
  • 前面windowShouldResize==true,即Activity窗口的大小发生了变化
  • 前面insetsChanged==true,即Activity窗口的内容区域边衬发生了变化
  • viewVisibilityChanged==true,Activity窗口的可见性发生了变化
  • 变量params != null,指向了一个WindowManager.LayoutParams对象,Activity窗口的属性发生了变化

当上面5中情况中一种情况为true,则activity窗口大小发生变化需要重新测量。并通过WMS请求计算activity窗口大小。然后开始测量流程。

            //mStopped==true,该窗口activity处于停止状态
            //mReportNextDraw,Window上报下一次绘制
            if (!mStopped || mReportNextDraw) {
                //触摸模式发生了变化,且检测焦点的控件发生了变化
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                //1. 焦点控件发生变化
                //2. 窗口宽高测量值 != WMS计算的mWinFrame宽高
                //3. contentInsetsChanged==true,边衬区域发生变化
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    //------开始执行测量操作--------
                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
                    //根据水平/垂直权重值判断是否重新测量
                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }

                    if (measureAgain) {
                        //----有相关权重,需要重新测量-----
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
  • 焦点控件发生变化
  • 窗口宽高测量值 != WMS计算的mWinFrame宽高
  • contentInsetsChanged==true,边衬区域发生变化

当上述情况之一出现则进行测量流程。测量完成后根据是否有配置权重进行再次测量。

        //layout布局要求标志位
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            //--------开始执行布局操作---------
            performLayout(lp, mWidth, mHeight);

            // By this point all views have been sized and positioned
            // We can compute the transparent area
            //计算透明区域
            ....
        }
        .....

        mFirst = false;

        ....

        // Remember if we must report the next draw.
        if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
            reportNextDraw();
        }

        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

        if (!cancelDraw && !newSurface) {
            ....
            //-----开始执行绘制操作-------
            performDraw();
        } else {
            if (isViewVisible) {
                // Try again
                scheduleTraversals();//重新执行performTraversals
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;

Invalidate、postInvalidate、requestLayout应用场景?
哪一个流程可以放在子线程中去执行?

参考资料
View绘制流程及源码解析(一)——performTraversals()源码分析
Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析
《Android开发艺术探索》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342