[关闭]
@linux1s1s 2019-02-15T15:12:38.000000Z 字数 5413 阅读 6103

Android requestLayout 和 invalidate

AndroidView 2015-07


这两个方法很多人 搞不太清楚,这里小结一下:

View的流程图

此处输入图片的描述
此处输入图片的描述

对于标题提及的两个方法 调用invalidate()或者requestLayout()会触发哪些方法,一图道破天机。

源代码

现在来看看具体的代码: android.view.ViewRootImpl

  1. @Override
  2. public void requestLayout() {
  3. if (!mHandlingLayoutInLayoutRequest) {
  4. checkThread();
  5. mLayoutRequested = true;
  6. scheduleTraversals();
  7. }
  8. }
  9. void invalidate() {
  10. mDirty.set(0, 0, mWidth, mHeight);
  11. if (!mWillDrawSoon) {
  12. scheduleTraversals();
  13. }
  14. }

这两个方法不约而同的调用了scheduleTraversals(),区别就是requsetLayout检查当前是否在主线程中,并且置位mLayoutRequsted = true,而invalidate方法没有检查。
接着我们进一步追踪下去看看scheduleTraversals()

  1. void scheduleTraversals() {
  2. if (!mTraversalScheduled) {
  3. mTraversalScheduled = true;
  4. mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
  5. mChoreographer.postCallback(
  6. Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  7. if (!mUnbufferedInputDispatch) {
  8. scheduleConsumeBatchedInput();
  9. }
  10. notifyRendererOfFramePending();
  11. }
  12. }

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

  1. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  2. final class TraversalRunnable implements Runnable {
  3. @Override
  4. public void run() {
  5. doTraversal();
  6. }
  7. }

进入doTraversal()方法:

  1. void doTraversal() {
  2. if (mTraversalScheduled) {
  3. mTraversalScheduled = false;
  4. mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
  5. if (mProfile) {
  6. Debug.startMethodTracing("ViewAncestor");
  7. }
  8. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
  9. try {
  10. performTraversals();
  11. } finally {
  12. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  13. }
  14. if (mProfile) {
  15. Debug.stopMethodTracing();
  16. mProfile = false;
  17. }
  18. }
  19. }

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

这个方法是ViewRootImpl最复杂的方法,将近800行代码Android View 分析(下)
这篇博文表粗略的分析了这个方法,这里不打算详细分析这个方法,我们仅仅做粗线条的了解, 进一步验证博文最上面的图是否正确:
ViewRootImpl这个类的L1767行:

  1. if (!mStopped) {
  2. boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
  3. (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
  4. if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
  5. || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
  6. int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  7. int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  8. if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
  9. + mWidth + " measuredWidth=" + host.getMeasuredWidth()
  10. + " mHeight=" + mHeight
  11. + " measuredHeight=" + host.getMeasuredHeight()
  12. + " coveredInsetsChanged=" + contentInsetsChanged);
  13. // Ask host how big it wants to be
  14. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  15. // Implementation of weights from WindowManager.LayoutParams
  16. // We just grow the dimensions as needed and re-measure if
  17. // needs be
  18. int width = host.getMeasuredWidth();
  19. int height = host.getMeasuredHeight();
  20. boolean measureAgain = false;
  21. if (lp.horizontalWeight > 0.0f) {
  22. width += (int) ((mWidth - width) * lp.horizontalWeight);
  23. childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
  24. MeasureSpec.EXACTLY);
  25. measureAgain = true;
  26. }
  27. if (lp.verticalWeight > 0.0f) {
  28. height += (int) ((mHeight - height) * lp.verticalWeight);
  29. childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
  30. MeasureSpec.EXACTLY);
  31. measureAgain = true;
  32. }
  33. if (measureAgain) {
  34. if (DEBUG_LAYOUT) Log.v(TAG,
  35. "And hey let's measure once more: width=" + width
  36. + " height=" + height);
  37. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  38. }
  39. layoutRequested = true;
  40. }
  41. }
  42. }

只要没有stop 并且

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

这个条件成立:
就会引发 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)

经过Debug可知:

  1. final boolean didLayout = layoutRequested && !mStopped;
  2. boolean triggerGlobalLayoutListener = didLayout
  3. || mAttachInfo.mRecomputeGlobalAttributes;
  4. if (didLayout) {
  5. performLayout(lp, desiredWindowWidth, desiredWindowHeight);
  6. ...
  7. }

所以到这里可以确认的是:
requsetLayout方法会调用performMeasure和performLayout方法
而invalidate方法则一个都不会调用。

我们接着往下看是否会接着调用performDraw方法

  1. if (!cancelDraw && !newSurface) {
  2. if (!skipDraw || mReportNextDraw) {
  3. if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  4. for (int i = 0; i < mPendingTransitions.size(); ++i) {
  5. mPendingTransitions.get(i).startChangingAnimations();
  6. }
  7. mPendingTransitions.clear();
  8. }
  9. performDraw();
  10. }
  11. }

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

  1. if (!cancelDraw && !newSurface) {
  2. if (!skipDraw || mReportNextDraw)
  3. }

一般情况下 以上条件都会满足,除非设置了明显的标志位,所以performDraw方法会执行。

我们来大概看一下上面的几个标志位:

cancelDraw 默认是false
skipDraw 默认是false
newSurface 位于:

  1. if (!hadSurface) {
  2. if (mSurface.isValid()) {
  3. // If we are creating a new surface, then we need to
  4. // completely redraw it. Also, when we get to the
  5. // point of drawing it we will hold off and schedule
  6. // a new traversal instead. This is so we can tell the
  7. // window manager about all of the windows being displayed
  8. // before actually drawing them, so it can display then
  9. // all at once.
  10. newSurface = true;
  11. mFullRedrawNeeded = true;
  12. mPreviousTransparentRegion.setEmpty();
  13. if (mAttachInfo.mHardwareRenderer != null) {
  14. try {
  15. hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
  16. mSurface);
  17. } catch (OutOfResourcesException e) {
  18. handleOutOfResourcesException(e);
  19. return;
  20. }
  21. }
  22. }
  23. }

首先最开始的判断分支不会成立,因为 hadSurface为true,所以newSurface = false

最后一个标志位mReportNextDraw:

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

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

我们小结一下:

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

  1. if (!cancelDraw && !newSurface) {
  2. if (!skipDraw || mReportNextDraw)
  3. }

则触发performDraw方法,反之,不会触发performDraw方法。

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

  1. if (!cancelDraw && !newSurface) {
  2. if (!skipDraw || mReportNextDraw)
  3. }

至此,上面的流程图我们得到验证。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注