深入理解 Android 之 View 的绘制流程(五)_invalidate,postInvalidate和requestLayout

上几篇文章里,我们分别介绍了View的三大工作流Measure,layout,draw。在分析源码的过程中我们会发现View的绘制流程还会受到其他方法的影响。比如:requestLayoutinvalidatepostInvalidate。下面我们来分别解析下这三个方法的不同调用。

requestLayout的源码分析

View#requestLayout

/**
*  view的layout发生改变的时候调用该方法。
*  如果当前View在请求布局的时候,View树正在进行布
*  局流程时,该请求会延迟到布局流程完成后或者绘制
*  流程完成且下一次布局发现的时候再执行。
*/
public void requestLayout() {
        //清除缓存的测量值,以便可以重新执行onMeasure
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
        //设置view的标志位:PFLAG_FORCE_LAYOUT
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            //向父容器请求布局,最终到达ViewRootImpl的requestLayout
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
}

可以看到执行方法requestLayout后,首先会清除掉测量缓存中保存的数据。然后判断当前View树是否正在布局流程,接着就为当前View设置标记位。最后向父容器请求布局,调用父容器的requestLayout方法。而父容器会又会调用它的父容器的requestLayout方法,直到ViewRootImpl中。在ViewRootImpl中重写了requestLayout的方法。

ViewRootImpl#requestLayout

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

方法中,调用了scheduleTraversals,在前面文章中,我们知道该方法是后面会调用performTraversals方法,这是开始View工作流的核心方法。从这里开始分别调用了measure,layout,draw方法。我们这里再回顾之前的measure方法:
View#measure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        .....

        //实例化一个对象用来保存测量的值
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        //判断mPrivateFlags标记为:PFLAG_FORCE_LAYOUT
        if ((mPrivateFlags & PFLAG_FORCE_LAPFLAG_FORCE_LAYOUTYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            .....

            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -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;
            }

            .....

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;

            .....
}

从上面可以看出,判断View标志位为:PFLAG_FORCE_LAYOUT,这时才会进入方法onMeasure开始测量。完成测量之后,再次设置View标志位为:PFLAG_LAYOUT_REQUIRED,这个标志作用于View的layout的流程中,标志了状态后才会进行layout流程。下面我们再看layout的源码:

View#layout

public void layout(int l, int t, int r, int b) {
        .....

        //判断标记位为:PFLAG_LAYOUT_REQUIRED
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            //清除PFLAG_LAYOUT_REQUIRED标记位
            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);
                }
            }
        }
        //清除requestLayout中设置的PFLAG_FORCE_LAYOUT标记位
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

所以到这里,requestLayout的流程便完成了。

总结:

View调用requestLayout后,首先会标记当前View和父容器的mPrivateFlags,然后到ViewRootImpl中。通过ViewRootImpl调用requestLayout方法,开始执行View的measure和layout流程,不会调用draw流程。

invalidate源码分析

invalidate主要是用来让View树进行重绘,如果需要刷新View的当前界面时在UI Thread中调用它,非UI Thread 中调用postInvalidate方法。

View#invalidate

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

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 (mGhostView != null) {
            mGhostView.invalidate(true);
            return;
        }
        //判断view状态是否可见,是否处于动画中
        if (skipInvalidate()) {
            return;
        }
        //判断mPrivateFlags标志是否是需要重绘,如果View没有任何变化,就不需要重绘
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }
            //设置mPrivateFlags标志为PFLAG_DIRTY
            mPrivateFlags |= PFLAG_DIRTY;

            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            // 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);
            }

            .....
        }
}

从上面可以看到调用invalidate后,最终调用的是invalidateInternal方法。在该方法中首先判断View的mPrivateFlags是否需要重绘,如果是的话,接着为View设置标志位:PFLAG_DIRTY,然后把需要绘制的区域传递给父容器,调用invalidateChild的方法。

ViewGroup#invalidateChild

public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;

        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            .....

            if (child.mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            //保存child的left和top的值
            final int[] location = attachInfo.mInvalidateChildLocation;
            location[CHILD_LEFT_INDEX] = child.mLeft;
            location[CHILD_TOP_INDEX] = child.mTop;
            .....

            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }

                .....

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                if (view != null) {
                    if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                            view.getSolidColor() == 0) {
                        opaqueFlag = PFLAG_DIRTY;
                    }
                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                        //对当前View的标记位进行设置
                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                    }
                }

                //调用ViewGrup的invalidateChildInParent,如果已经达到最顶层view,则调用ViewRootImpl的invalidateChildInParent。
                parent = parent.invalidateChildInParent(location, dirty);

                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) (boundingRect.left - 0.5f),
                                (int) (boundingRect.top - 0.5f),
                                (int) (boundingRect.right + 0.5f),
                                (int) (boundingRect.bottom + 0.5f));
                    }
                }
            } while (parent != null);
        }
}

该方法中设置先设置当前标志位,然后在do while 循环中执行ViewGroup中的invalidateChildInParent方法。

ViewGroup#invalidateChildInParent

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
                (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                        FLAG_OPTIMIZE_INVALIDATE) {

                //将dirty中的坐标转化为父容器中的坐标,考虑mScrollX和mScrollY的影响
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                    //求并集,结果是把子视图的dirty区域转化为父容器的dirty区域
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                final int left = mLeft;
                final int top = mTop;

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                        dirty.setEmpty();
                    }
                }
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

                //记录当前视图的mLeft和mTop值,在下一次循环中会把当前值再向父容器的坐标转化
                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;

                if (mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                }

                //返回当前视图的父容器
                return mParent;

            } else {
                mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;

                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    // in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                if (mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                }

                return mParent;
            }
        }

        return null;
}

在do while中不断循环该方法,最后都返回当前View的父容器,最后会调用到ViewRootImpl的invalidateChildInParent方法。

ViewRootImpl#invalidateChildInParent

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);

    if (dirty == null) {
        invalidate();
        return null;
    } else if (dirty.isEmpty() && !mIsAnimating) {
        return null;
    }

    if (mCurScrollY != 0 || mTranslator != null) {
        mTempRect.set(dirty);
        dirty = mTempRect;
        if (mCurScrollY != 0) {
            dirty.offset(0, -mCurScrollY);
        }
        if (mTranslator != null) {
            mTranslator.translateRectInAppWindowToScreen(dirty);
        }
        if (mAttachInfo.mScalingRequired) {
            dirty.inset(-1, -1);
        }
    }

    invalidateRectOnScreen(dirty);

    return null;
}


private void invalidateRectOnScreen(Rect dirty) {
    final Rect localDirty = mDirty;
    if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
        mAttachInfo.mSetIgnoreDirtyState = true;
        mAttachInfo.mIgnoreDirtyState = true;
    }

    // Add the new dirty rect to the current one
    localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
    // Intersect with the bounds of the window to skip
    // updates that lie outside of the visible region
    final float appScale = mAttachInfo.mApplicationScale;
    final boolean intersected = localDirty.intersect(0, 0,
            (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    if (!intersected) {
        localDirty.setEmpty();
    }
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

从上可以看到invalidateChildInParent方法中,调用了方法invalidateRectOnScreen,而在invalidateRectOnScreen中又执行了scheduleTraversals。后面的流程就和requestLayout的中一样了。都是通过View的标记位,不断的刷新父容器需要重绘的区域,知道传递到ViewRootImpl中,最终还是执行performTraversals方法。然后整个View树重新开始按照上面分析的View绘制流程进行重绘任务。

postInvalidate源码分析

在分析invalidate中我们说过,invalidate方法主要是执行在UI Thread中,而postInvalidate用于在其他线程中执行。我们来分析这个方法:

View#postInvalidate

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

public void postInvalidateDelayed(long delayMilliseconds) {
    // We try only with the AttachInfo because there's no point in invalidating
    // if we are not attached to our window
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}

从上面分析,View执行postInvalidate时,调用的是postInvalidateDelayed的方法。而该方法中调用的是ViewRootImpl中的dispatchInvalidateDelayed方法。所有我们去ViewRootImpl类中查看该方法。

ViewRootImpl#dispatchInvalidateDelayed

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

.....

final ViewRootHandler mHandler = new ViewRootHandler();

.....

final class ViewRootHandler extends Handler {
        @Override
        public String getMessageName(Message message) {
            .....

            return super.getMessageName(message);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_INVALIDATE:
                ((View) msg.obj).invalidate();
                break;
            .....
        }
}

从上面可以知道,原来在dispatchInvalidateDelayed方法中使用的handler来处理线程间的通讯,这样就避免了在子线程中进行刷新UI的操作。在handler中的handleMessage方法里,调用了当前View的invalidate,之后的流程与直接调用invalidate中一样,这里就不再做分析。
到这里我们就完成了requestLayoutinvalidatepostInvalidate三个方法的分析。

总结:

1:如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局时,这时就会使用requestLayout。(执行requestLayout时, 不会调用draw流程)
2:如果需要刷新当前View的内容,使当前View进行重绘,不会调用测量、布局流程,那么选择调用invalidate会比requestLayout更加高效。

猜你喜欢

转载自blog.csdn.net/yuminfeng728/article/details/76101121