对于Android View的自定义来说,这三兄弟简直是我们必须要拿下的坎,那么这三个方法有什么本质上的区别呢?

我个人认为要从本质上来认识这三者的区别,那么你肯定要对Android View的绘制流程了如指掌。首先我们先来看看View 的绘制流程:

ResolverActivity android源码 默认 android invalidate源码_ci


ResolverActivity android源码 默认 android invalidate源码_Android_02


从上面View的绘制流程图上我们对invalidate 、requestLayout会触发哪些流程一目了然。

接下来我们用源码的角度上来进行更深入的剖析!

requestLayout

当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。
具体的源码:frameworks/base/core/java/android/view/ViewRootImpl.java
我们先对比invalidate 跟 requestLayout的本质区别

1、requestLayout

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            if (DEBUG_LAYOUT) {
                Log.d(TAG, "requestLayout: mView = " + mView + ", this = " + this);
            }
            if (DEBUG_REQUESTLAYOUT) {
                Log.d(TAG, "requestLayout: mView = " + mView + ", this = " + this,
                        new Throwable("requestLayout"));
            }
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

2、invalidate

void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            scheduleTraversals();
        }
    }

分析:
从上面的源码上可以看出这两个方法不约而同的调用了scheduleTraversals(),区别就是requsetLayout检查当前是否在主线程中,并且置位mLayoutRequsted = true,而invalidate方法没有检查。

3、检查线程checkThread

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

4、进一步追踪下去看看scheduleTraversals()做什么事

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            if (!scheduleByHandler) {
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            }
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里会post一个runnable请求就是mTraversalRunnable

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

5、进入doTraversal()方法

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
            performTraversals();
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

注意这个方法中,我们重点关注performTraversals()方法:

if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    if (DEBUG_LAYOUT) {
                        Log.v(TAG, "Ooops, something changed! mWidth=" + mWidth
                                + " measuredWidth=" + host.getMeasuredWidth() + " mHeight="
                                + mHeight + " measuredHeight=" + host.getMeasuredHeight()
                                + " contentInsetsChanged=" + contentInsetsChanged
                                + " this = " + this);
                    }

                     // 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) {
                        if (DEBUG_LAYOUT) {
                            Log.v(TAG, "And hey let's measure once more: width=" + width
                                    + " height=" + height + " this = " + this);
                        }
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }

只要没有stop 并且

if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged)

这个条件成立,就是触发

performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

经过Debug模式调试得出以下结论:

  • requsetLayout触发performTraversal方法,进而调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),进一步的调用performLayout方法,此时layoutRequested = true
  • invalidate触发performTraversal方法,因为上面的条件不成立,所以不会调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),同样也不会调用performLayout方法
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;

        if (DEBUG_LAYOUT || DEBUG_MEASURE_LAYOUT) {
            Log.v(TAG, "ViewRoot layout+ : " + host + ", layoutRequested = " + layoutRequested
                    + ", mStopped = " + mStopped + ", this = " + this);
        }

        if (didLayout) {
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);

所以到这里可以确认的是:
requsetLayout方法会调用performMeasure和performLayout方法
而invalidate方法则一个都不会调用。
我们接着往下看是否会接着调用performDraw方法

if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }

                performDraw();
                //FLYME:HuangTao@Shell.Glass Blur.Feature {@
                performBlurGlass();
                //@}
            }

由上面的代码可知:需满足以下几个条件才会触发performDraw方法

if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
       }

一般情况下 以上条件都会满足,除非设置了明显的标志位,所以performDraw方法会执行。
那么接下来我们再来研究上面的几个标志位:

cancelDraw 默认是false 
skipDraw 默认是false 
newSurface 位于:
if (!hadSurface) {
                    if (mSurface.isValid()) {
                        if (DEBUG_DRAW) {
                            Log.v(TAG, "Create new surface: " + mSurface
                                    + ",surfaceGenerationId = " + surfaceGenerationId
                                    + ", mSurface.getGenerationId() = " + mSurface.getGenerationId()
                                    + ",mPreviousTransparentRegion = " + mPreviousTransparentRegion
                                    + ",this = " + this);
                        }
                        // 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.
                        /// M: [APP launch time enhancenment feature] Ignore newSurface flag, if
                        /// this window is a kind of starting type. @{
                        if (!DBG_APP_LAUNCH_ENHANCE || lp.type !=
                                    WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
                            newSurface = true;
                        }
                        /// @}
                        mFullRedrawNeeded = true;
                        mPreviousTransparentRegion.setEmpty();

                        // Only initialize up-front if transparent regions are not
                        // requested, otherwise defer to see if the entire window
                        // will be transparent
                        if (mAttachInfo.mHardwareRenderer != null) {
                            try {
                                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "HW init");
                                hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
                                        mSurface);
                                if (hwInitialized && (host.mPrivateFlags
                                        & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
                                    // Don't pre-allocate if transparent regions
                                    // are requested as they may not be needed
                                    mSurface.allocateBuffers();
                                }
                                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                            } catch (OutOfResourcesException e) {
                                handleOutOfResourcesException(e);
                                return;
                            }
                        }
                    }

首先最开始的判断分支不会成立,因为 hadSurface为true,所以newSurface = false
最后一个标志位mReportNextDraw:

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

这里在执行判断该标志位之前就置位为true。所以一般会进入performDraw方法

总结:
requsetLayout方法会触发performMeasure和performLayout,而performDraw方法是否触发视情况而定
如果以下条件满足

if (!cancelDraw && !newSurface) {
    if (!skipDraw || mReportNextDraw) 
}

则触发performDraw方法,反之,不会触发performDraw方法。
而invalidate方法则只会触发performDraw方法,因为一般情况下条件都会满足:

if (!cancelDraw && !newSurface) {
    if (!skipDraw || mReportNextDraw) 
}

至此我们从源码上已经分析完Invalidate 跟 requestLayout,那么我接下来大概说一下postInvalidate

6、postInvalidate

postInvalidate跟invalidate是一样的,只不过它不是在UI线程上更新而已。

结论:三者的区别

  1. If you want to re draw your view from UI Thread you can call invalidate() method.
  2. If you want to re draw your view from Non UI Thread you can call postInvalidate() method.
  3. onDraw是在View初化完成之后开始调用postInvalidate()是重绘的,也就是调用postInvalidate()后系统会重新调用onDraw方法画一次
  4. Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。
  5. Android程序中可以使用的界面刷新方法有两种,分别是利用Handler和利用postInvalidate()来实现在线程中刷新界面
  6. Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。 invalidate()是用来刷新View的,必须是在UI线程中进行工作。比如在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面。invalidate()的调用是把之前的旧的view从主UI线程队列中pop掉。 一个Android 程序默认情况下也只有一个进程,但一个进程下却可以有许多个线程。在这么多线程当中,把主要是负责控制UI界面的显示、更新和控件交互的线程称为UI线程,由于onCreate()方法是由UI线程执行的,所以也可以把UI线程理解为主线程。其余的线程可以理解为工作者线程。invalidate()得在UI线程中被调动,在工作者线程中可以通过Handler来通知UI线程进行界面更新。而postInvalidate()在工作者线程中被调用