Android窗口管理5 理解ViewRootImpl

一 概述

ViewRootImpl 是一个视图层次结构的顶部,可以理解为一个 Window 中所有 View 的根 View 的管理者(但 ViewRootImpl 不是 View,只是实现了 ViewParent 接口),实现了 View 和 WindowManager 之间的通信协议,实现的具体细节在 WindowManagerGlobal 这个类中。

在这里插入图片描述

简单来说 ViewRootImpl 是 View 与 WindowManager 之间联系的桥梁,作用总结如下:

1.将 DecorView 传递给 WindowManagerSerive
2.完成 View 的绘制过程,包括 measure、layout、draw 过程
3.向 DecorView 分发收到的用户发起的 event 事件,如按键,触屏等事件。

其中,ViewRootImpl 中包含了两个需要重点关注的内部类:

  • final class ViewRootHandler extends Handler 用于向 DecorView 分发事件
  • static class W extends IWindow.Stub

W 是 ViewRootImp l的一个嵌入类,也是一个 Binder 服务。通过 mWindowSession.addToDisplay 函数传入 WMS,用来在 WMS 中通过 Binder 回调。

二 将Window传递给WMS

public void setView(View view, WindowManager.LayoutParams attrs,
        View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
               ......
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();//见下节介绍
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.
                        LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();//生成InputChannel
                }
                ......
                try {
                    ......
                    //调用mWindowSession.addToDisplay通过binder调用到WMS
                    //实现对Window的真正的添加,这里的mWindow为 W 对象
                    res = mWindowSession.addToDisplay(mWindow, mSeq, ......);
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                    ......
                } finally {
                    ......
                }
                ......               
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    //用于输入事件的接收
                    mInputEventReceiver = 
                    new WindowInputEventReceiver(
                    mInputChannel, Looper.myLooper());
                }
               ......
            }
        }
    }

三 View的绘制

3.1 requestLayout

frameworks/base/core/java/android/view/View.java

public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();
	// 步骤1
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }

	// 步骤2
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

调用时机:
当某些变化导致原有 View 树的 layout 发生变化,可以调用 requestLayout 重新布局 View 树。

1.关于 requestLayout 的叠加

  • 尽量不要在 layout 过程中又调用 requestLayout
  • 如果非要叠加调用 requestLayout,则将当前作出该请求的 View 先放入 mLayoutRequesters 列表,等到调用 ViewRootImpl#performLayout 的时候再取出 mLayoutRequesters 列表依次调用各个 View#requestLayout。如果当前正在处理 mLayoutRequesters 列表,则不要再次触发 requestLayout

2.mParent.requestLayout()

  • 我们先搞清楚 mParent 是啥?
    mParent 的类型是 ViewParent,通过代码溯源 View#assignParent,可以得出如下结论:
    DecorView (即根 View) 对应的 mParent 是 ViewRootImpl,普通子 View (非根 View) 对应的 mParent 是子 View 的父 View (即 ViewGroup)

  • requestLayout 调用过程?
    该过程是一个从子 View -> 父 View -> DecorView -> ViewRootImpl 层层往上的递归调用过程

3.2 invalidate

frameworks/base/core/java/android/view/View.java

public void invalidate() {
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft,
    mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b,
    boolean invalidateCache, boolean fullInvalidate) {
    if (skipInvalidate()) {
        return;
    }
    ......
    // Propagate the damage rectangle to the parent view.
    final AttachInfo ai = mAttachInfo;
    final ViewParent p = mParent;
    if (p != null && ai != null && l < r && t < b) {
        final Rect damage = ai.mTmpInvalRect;
        damage.set(l, t, r, b);
        p.invalidateChild(this, damage);
    }
    ......
}

frameworks/base/core/java/android/view/ViewGroup.java

@Override
public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        // HW accelerated fast path
        onDescendantInvalidated(child, child);
        return;
    }

    ViewParent parent = this;
    if (attachInfo != null) {
        final int[] location = attachInfo.mInvalidateChildLocation;
        location[CHILD_LEFT_INDEX] = child.mLeft;
        location[CHILD_TOP_INDEX] = child.mTop;
		......
        do {
            ......
            parent = parent.invalidateChildInParent(location, dirty);
            ......
        } while (parent != null);
    }
}

1.调用时机:
当前 View 树需要重绘时。如果当前 View 可见,则会调到 onDraw 方法。
该方法必须在 UI 线程调用。

2.View#invalidate 的调用逻辑:
从子 View -> 父 View -> DecorView -> ViewRootImpl 从子到父层层调用。

  • 硬件加速
    ViewGroup#onDescendantInvalidated -> …-> DecorView#onDescendantInvalidated -> ViewRootImpl#onDescendantInvalidated

  • 非硬件加速
    ViewGroup#invalidateChild -> ViewGroup#invalidateChildInParent -> … -> DecorView#invalidateChildInParent -> ViewRootImpl#invalidateChildInParent

3.skipInvalidate 逻辑

private boolean skipInvalidate() {
    return (mViewFlags & VISIBILITY_MASK) != VISIBLE && 
        mCurrentAnimation == null &&
            (!(mParent instanceof ViewGroup) ||
                    !((ViewGroup) mParent).isViewTransitioning(this));
}

如果当前 View 不可见并且当前没有动画时,则不会 invalidate 执行重绘.

3.3 postInvalidate

frameworks/base/core/java/android/view/View.java

public void postInvalidate() {
    postInvalidateDelayed(0);
}

public void postInvalidateDelayed(long delayMilliseconds) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(
            this, delayMilliseconds);
    }
}

frameworks/base/core/java/android/view/ViewRootImpl.java

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

调用时机:
和 invalidate 类似。
不同的是,postInvalidate 通过发送一个消息到主线程的消息队列,在消息队列中执行 invalidate 方法。
因此 postInvalidate 可以在非 UI 线程调用。

3.4 requestLayout & invalidate & postInvalidate

相同点:

  • 都是从子 View -> 父 View -> DecorView -> ViewRootImpl 从子到父层层调用
  • 都会导致 View 树重绘,最终都会调用到 ViewRootImpl#scheduleTraversals

不同点:

  • requestLayout 在 UI 线程调用
  • invalidate 在 UI 线程调用。当 View 树可见时,会调用到 onDraw
  • postInvalidate 可以在非 UI 线程调用。逻辑和 invalidate 类似,不同点是 postInvalidate 通过发消息方式,使 invalidate 操作在消息队列里有序执行

四 ViewRootImpl的绘制

frameworks/base/core/java/android/view/ViewRootImpl.java

4.1 scheduleTraversals

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 添加同步屏障
        mTraversalBarrier = 
            mHandler.getLooper().getQueue().postSyncBarrier();
        // 发送并执行遍历操作
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
            mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(
            mTraversalBarrier); 
        performTraversals();
    }
}

同步屏障 SyncBarrier
当调用 postSyncBarrier 后,MessageQueue 中的同步消息将不能执行,直到 removeSyncBarrier 才会执行。这个不影响异步消息。

在设置了同步屏障后,发送一个 CALLBACK_TRAVERSAL 类型消息到 Choreographer 的消息队列。
在移除了同步屏障后,执行 performTraversals

4.2 performTraversals

private void performTraversals() {
    ......
    //如果当前View树中包含SurfaceView, 则执行surfaceCreated/surfaceChanged回调
    if (mSurfaceHolder != null) {
        if (mSurface.isValid()) {
            mSurfaceHolder.mSurface = mSurface;
        }
        mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight);
        if (mSurface.isValid()) {
            if (!hadSurface) {
                mSurfaceHolder.ungetCallbacks();
 
                mIsCreating = true;
                SurfaceHolder.Callback callbacks[] =
                    mSurfaceHolder.getCallbacks();
                if (callbacks != null) {
                    for (SurfaceHolder.Callback c : callbacks) {
                        c.surfaceCreated(mSurfaceHolder);
                    }
                }
                surfaceChanged = true;
            }
            if (surfaceChanged || surfaceGenerationId !=
                mSurface.getGenerationId()) {
                SurfaceHolder.Callback callbacks[] =
                    mSurfaceHolder.getCallbacks();
                if (callbacks != null) {
                    for (SurfaceHolder.Callback c : callbacks) {
                        c.surfaceChanged(mSurfaceHolder, lp.format,
                            mWidth, mHeight);
                    }
                }
            }
            mIsCreating = false;
        }
    } 
    ......
    // mWidth&mHeight为Frame宽高, lp为setView传进来的
    // WindowManager.LayoutParams参数
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(lp, mWidth, mHeight);
    ......
    // 调用OnGlobalLayoutListener#onGlobalLayout
    if (triggerGlobalLayoutListener) {
        mAttachInfo.mRecomputeGlobalAttributes = false;
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    ......
    performDraw();
    ......
}

主要做了一下工作:

  • 如果 View 树中当前 View 为 SurfaceView,则执行 surfaceCreated / surfaceChanged 相关回调
  • 依次执行 performMeasure -> performLayout -> performDraw
  • OnGlobalLayoutListener#onGlobalLayout 回调中可以获取到 View 的真实宽高。因为该方法在 performMeasure -> performLayout 后面执行

4.2.1 performMeasure

4.2.1.1 ViewRootImpl.performMeasure
private void performMeasure(int childWidthMeasureSpec,
    int childHeightMeasureSpec) {
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
4.2.1.2 View.measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,
            optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec,
            optical ? -oHeight : oHeight);
    }
 
    // Suppress sign extension for the low bytes
    long key = (long) widthMeasureSpec << 32 |
        (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
 
    final boolean forceLayout =
        (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
 
    // Optimize layout by avoiding an extra EXACTLY pass when the view is
    // already measured as the correct size. In API 23 and below, this
    // extra pass is required to make LinearLayout re-distribute weight.
    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
            || heightMeasureSpec != mOldHeightMeasureSpec;
    final boolean isSpecExactly = 
    MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
        && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    final boolean matchesSpecSize =
    getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
        && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
        && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
 
    if (forceLayout || needsLayout) {
        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
 
        resolveRtlPropertiesIfNeeded();
 
        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set 
            // the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
        // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
 
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
 
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
 
    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
        (long) mMeasuredHeight & 0xffffffffL); //suppress sign extension
}
4.2.1.3 View.onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
    widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),
    heightMeasureSpec));
}
4.2.1.4 View.setMeasuredDimension
protected final void setMeasuredDimension(int measuredWidth,
    int measuredHeight) {
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight; 
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

1.Measure 过程调用链路
ViewRootImpl#performMeasure -> View#measure -> View#onMeasure -> View#setMeasureDimension

2.缓存策略
为了避免每次重复测量,采用了缓存策略。测量缓存数据结构 LongSparseLongArray
其中 key 为 MeasureSpec,value 为 MeasuredWidth/MeasuredHeight。高 32 位代表宽度,低 32 位代表高度。

// key
(long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL  
// value
(long) mMeasuredWidth << 32 |  (long) mMeasuredHeight & 0xffffffffL

3.测量过程
如果缓存中没有,则需要测量方法 View#onMeasure。具体的测量宽高方式参考 getDefaultSize。
其中 size 为建议大小 getSuggestedMinimumWidth/Height,measureSpec 为测量规范。

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth :
        max(mMinWidth, mBackground.getMinimumWidth());
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
 
    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

4.MeasureSpec
上面提到了 int measureSpec 为测量规范。怎么理解这个测量规范呢? 可以用类 MeasureSpec 来描述。
MeasureSpec 由 mode 和 size 组成,前 2 位为 mode,后 30 位为 size。之所以这么设计,是出于节省内存考虑。有三种 mode 类型:

  • UNSPECIFIED 父 View 没有对子 View 做限制
  • EXACTLY 父 View 指定了子 View 的精确大小
  • AT_MOST 父 View 指定了子 View 的最大值,子 View 最大到达这个最大值
public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
    @Retention(RetentionPolicy.SOURCE)
    public @interface MeasureSpecMode {}

    public static int makeMeasureSpec(@IntRange(from = 0,
    to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
    @MeasureSpecMode int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }

    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}

4.2.2 performLayout

4.2.2.1 ViewRootImpl.performLayout
private void performLayout(WindowManager.LayoutParams lp,
    int desiredWindowWidth, int desiredWindowHeight) {
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
}
4.2.2.2 View.layout
public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
 
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
 
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
 
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) ==
    PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
 
        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }
 
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
 
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                (ArrayList<OnLayoutChangeListener>)
                li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(
                this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
 
    final boolean wasLayoutValid = isLayoutValid();
 
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
 
    if (!wasLayoutValid && isFocused()) {
        mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
        if (canTakeFocus()) {
            // We have a robust focus, so parents 
            // should no longer be wanting focus.
            clearParentsWantFocus();
        } else if (getViewRootImpl() == null ||
            !getViewRootImpl().isInLayout()) {
            clearFocusInternal(null, /* propagate */ true,
            /* refocus */ false);
            clearParentsWantFocus();
        } else if (!hasParentWantsFocus()) {
            clearFocusInternal(null, /* propagate */ true,
            /* refocus */ false);
        }
    } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
        mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
        View focused = findFocus();
        if (focused != null) {
            if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
                focused.clearFocusInternal(null, /* propagate */ true,
                /* refocus */ false);
            }
        }
    }
 
    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    }
}

Layout过程调用链路:
ViewRootImpl#performLayout -> View#layout -> View#onLayout

4.2.3 performDraw

4.2.3.1 ViewRootImpl.performDraw
private void performDraw() {
    ......
    final Canvas canvas = mSurface.lockCanvas(dirty);
    canvas.setDensity(mDensity);
    canvas.translate(-xoff, -yoff);
    canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
    ......
    mView.draw(canvas);
    surface.unlockCanvasAndPost(canvas);
}

performDraw 调用流程:
draw(fullRedrawNeeded) -> drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets) -> mView.draw(canvas)

4.2.3.2 View.draw
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
 
    // Step 1, draw the background, if needed
    int saveCount; 
    drawBackground(canvas);
 
    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        onDraw(canvas);
 
        // Step 4, draw the children
        dispatchDraw(canvas);
 
        drawAutofilledHighlight(canvas);
 
        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
 
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
 
        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);
 
        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
 
        // we're done...
        return;
    }
 
    boolean drawTop = false;
    boolean drawBottom = false;
    boolean drawLeft = false;
    boolean drawRight = false;
 
    float topFadeStrength = 0.0f;
    float bottomFadeStrength = 0.0f;
    float leftFadeStrength = 0.0f;
    float rightFadeStrength = 0.0f;
 
    // Step 2, save the canvas' layers
    int paddingLeft = mPaddingLeft;
 
    final boolean offsetRequired = isPaddingOffsetRequired();
    if (offsetRequired) {
        paddingLeft += getLeftPaddingOffset();
    }
 
    int left = mScrollX + paddingLeft;
    int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    int top = mScrollY + getFadeTop(offsetRequired);
    int bottom = top + getFadeHeight(offsetRequired);
 
    if (offsetRequired) {
        right += getRightPaddingOffset();
        bottom += getBottomPaddingOffset();
    }
 
    final ScrollabilityCache scrollabilityCache = mScrollCache;
    final float fadeHeight = scrollabilityCache.fadingEdgeLength;
    int length = (int) fadeHeight;
 
    // clip the fade length if top and bottom fades overlap
    // overlapping fades produce odd-looking artifacts
    if (verticalEdges && (top + length > bottom - length)) {
        length = (bottom - top) / 2;
    }
 
    // also clip horizontal fades if necessary
    if (horizontalEdges && (left + length > right - length)) {
        length = (right - left) / 2;
    }
 
    if (verticalEdges) {
        topFadeStrength = Math.max(0.0f, Math.min(1.0f,
            getTopFadingEdgeStrength()));
        drawTop = topFadeStrength * fadeHeight > 1.0f;
        bottomFadeStrength = Math.max(0.0f, Math.min(1.0f,
            getBottomFadingEdgeStrength()));
        drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
    }
 
    if (horizontalEdges) {
        leftFadeStrength = Math.max(0.0f, Math.min(1.0f,
            getLeftFadingEdgeStrength()));
        drawLeft = leftFadeStrength * fadeHeight > 1.0f;
        rightFadeStrength = Math.max(0.0f, Math.min(1.0f,
            getRightFadingEdgeStrength()));
        drawRight = rightFadeStrength * fadeHeight > 1.0f;
    }
 
    saveCount = canvas.getSaveCount();
    int topSaveCount = -1;
    int bottomSaveCount = -1;
    int leftSaveCount = -1;
    int rightSaveCount = -1;
 
    int solidColor = getSolidColor();
    if (solidColor == 0) {
        if (drawTop) {
            topSaveCount = canvas.saveUnclippedLayer(
                left, top, right, top + length);
        }
 
        if (drawBottom) {
            bottomSaveCount = canvas.saveUnclippedLayer(
                left, bottom - length, right, bottom);
        }
 
        if (drawLeft) {
            leftSaveCount = canvas.saveUnclippedLayer(
                left, top, left + length, bottom);
        }
 
        if (drawRight) {
            rightSaveCount = canvas.saveUnclippedLayer(
                right - length, top, right, bottom);
        }
    } else {
        scrollabilityCache.setFadeColor(solidColor);
    }
 
    // Step 3, draw the content
    onDraw(canvas);
 
    // Step 4, draw the children
    dispatchDraw(canvas);
 
    // Step 5, draw the fade effect and restore layers
    final Paint p = scrollabilityCache.paint;
    final Matrix matrix = scrollabilityCache.matrix;
    final Shader fade = scrollabilityCache.shader;
 
    // must be restored in the reverse order that they were saved
    if (drawRight) {
        matrix.setScale(1, fadeHeight * rightFadeStrength);
        matrix.postRotate(90);
        matrix.postTranslate(right, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(rightSaveCount, p);
        } else {
            canvas.drawRect(right - length, top, right, bottom, p);
        }
    }
 
    if (drawLeft) {
        matrix.setScale(1, fadeHeight * leftFadeStrength);
        matrix.postRotate(-90);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(leftSaveCount, p);
        } else {
            canvas.drawRect(left, top, left + length, bottom, p);
        }
    }
 
    if (drawBottom) {
        matrix.setScale(1, fadeHeight * bottomFadeStrength);
        matrix.postRotate(180);
        matrix.postTranslate(left, bottom);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(bottomSaveCount, p);
        } else {
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }
    }
 
    if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(topSaveCount, p);
        } else {
            canvas.drawRect(left, top, right, top + length, p);
        }
    }
 
    canvas.restoreToCount(saveCount);
 
    drawAutofilledHighlight(canvas);
 
    // Overlay is part of the content and draws beneath Foreground
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }
 
    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
 
    if (debugDraw()) {
        debugDrawFocus(canvas);
    }
}

1.调用链路
ViewRootImpl#performDraw -> View#draw

2.draw由下到上的绘制顺序

  • drawBackground 绘制背景
  • save layer (当有水平或垂直fading edges时)
  • onDraw 绘制当前View的内容
  • dispatchDraw 绘制当前View的子View
  • 绘制 fading edges 和 restore layers (当有水平或垂直 fading edges 时)
  • onDrawForeground 绘制前景或滚动条
  • drawDefaultFocusHighlight 绘制获取焦点的 View 的 Focus 高亮

五 常见问题

1.什么时候获取 View 的测量宽高?

private void performTraversals() {
    ......
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(lp, mWidth, mHeight);
    ......
    if (triggerGlobalLayoutListener) {
        mAttachInfo.mRecomputeGlobalAttributes = false;
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    ......
    performDraw();
    ......
}

根据 ViewRootImpl#scheduleTraversals 的调用逻辑。dispatchOnGlobalLayout 在 performMeasure -> performLayout 之后调用,通过该回调可以获取到测量的宽高。

App 层可以通过注册 OnGlobalLayoutListener 方式获取

view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
		// view.getMeasuredWidth()/view.getMeasuredHeight()
		......
    }
});

2.在子线程中可以更新 UI 吗?

在 Activity#onResume 之前,可以在子线程中更新 UI。
checkThread 线程检查是在 ViewRootImpl 的方法中进行的 (如 invalidate 和 requestLayout)
而通过溯源代码,ViewRootImpl 是在 ActivityThread#handleResumeActivity 创建的。在 Activity#onResume 之前 ViewRootImpl 还没创建,所以也不会检查线程和绘制 UI。

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    ......
}

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a" +  
                "view hierarchy can touch its views.");
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值