美文网首页
performTraversals三大流程开始之地

performTraversals三大流程开始之地

作者: KIDNG_LGJ | 来源:发表于2018-09-18 14:04 被阅读0次

    工作一段时间了,但是感觉自己对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开发艺术探索》

    相关文章

      网友评论

          本文标题:performTraversals三大流程开始之地

          本文链接:https://www.haomeiwen.com/subject/iirpiftx.html