当前位置: 首页 > 编程笔记 >

浅谈Android invalidate 分析

甘英光
2023-03-14
本文向大家介绍浅谈Android invalidate 分析,包括了浅谈Android invalidate 分析的使用技巧和注意事项,需要的朋友参考一下

1. invalidate 和 postInvalidate 的关系

postInvalidate 是通过 Handler 切换回到主线程,然后在调用 invalidate 的,源码:

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

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

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

2. 子线程是否可以更新 UI ?

可以的,在 Activity 的 onCreate 中直接开启子线程并在子线程中更新 UI 是没问题的:

public class MainActivity extends Activity {
  private TextView tvText;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tvText = (TextView) findViewById(R.id.main_tv);
    new Thread(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(200);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        tvText.setText("OtherThread");
      }
    }).start();
  }
}

原因:校验线程是 ViewRootImpl 来做的,但是它的创建流程是在 Activity 的 onResume 的时候:

// ActivityThread 中
  final void handleResumeActivity(IBinder token,
      boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    ...
    if (r != null) {
      final Activity a = r.activity;
      if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        ...
        if (a.mVisibleFromClient) {
          if (!a.mWindowAdded) {
            a.mWindowAdded = true;
            // 关键代码
            wm.addView(decor, l);
          } else {
            a.onWindowAttributesChanged(l);
          }
        }
        ...
  }

  // WindowManagerGlobal 中
  public void addView(View view, ViewGroup.LayoutParams params,
      Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
      ...
      // 在这里创建 ViewRootImpl
      root = new ViewRootImpl(view.getContext(), display);

      view.setLayoutParams(wparams);
      ...
    }
  }

  // 在 ViewRootImpl 中有这么段代码,所有更新 UI 都会走到这里
  void checkThread() {
    if (mThread != Thread.currentThread()) { // mThread 就是主线程
      throw new CalledFromWrongThreadException(
          "Only the original thread that created a view hierarchy can touch its views.");
    }
  }

所以子线程只要在 ViewRootImpl 创建之前更新 UI 就没问题!

3. invalidate 的源码分析

先看一张图:

invalidate 的流程

于是自己尝试走走源码:

// view 中
  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 (mGhostView != null) {
      mGhostView.invalidate(true);
      return;
    }
    if (skipInvalidate()) {
      return;
    }
    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;
      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);
        // 调用父类的 invalidateChild 方法
        p.invalidateChild(this, damage);
      }

      // Damage the entire projection receiver, if necessary.
      if (mBackground != null && mBackground.isProjected()) {
        final View receiver = getProjectionReceiver();
        if (receiver != null) {
          receiver.damageInParent();
        }
      }
    }
  }

看到 View 的 invalidate 最后是调用了 p.invalidateChild(this, damage); p 是 ViewParent 的对象,具体实现是 ViewGroup

// ViewGroup 中
  @Override
  public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    ...
    ViewParent parent = this;
      do {
        View view = null;
        ...
        // 关键代码
        parent = parent.invalidateChildInParent(location, dirty);
        ...
    } while (parent != null);
  }
  @Override
  public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
      // either DRAWN, or DRAWING_CACHE_VALID
      if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
          != FLAG_OPTIMIZE_INVALIDATE) {
        dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
            location[CHILD_TOP_INDEX] - mScrollY);
        if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
          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();
          }
        }

        location[CHILD_LEFT_INDEX] = left;
        location[CHILD_TOP_INDEX] = top;
      } else {

        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);
        }
        location[CHILD_LEFT_INDEX] = mLeft;
        location[CHILD_TOP_INDEX] = mTop;

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

      return mParent;
    }
    return null;
  }

上面 invalidateChildInParent 开始时会调用 ViewGroup 自己的 invalidateChildInParent 方法,但到最后还是会调用到 ViewRootImpl 中的 invalidateChildInParent,看下 ViewRootImpl 中的具体实现

// ViewRootImpl 中
  @Override
  public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    if (DEBUG_DRAW) Log.v(mTag, "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();
    }
  }

  void scheduleTraversals() {
    if (!mTraversalScheduled) {
      mTraversalScheduled = true;
      mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
      // 会调用 mTraversalRunnable 中的 run 方法
      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);
      if (mProfile) {
        Debug.startMethodTracing("ViewAncestor");
      }
      // 终于到了关键方法了:
      performTraversals();

      if (mProfile) {
        Debug.stopMethodTracing();
        mProfile = false;
      }
    }
  }

ViewRootImpl 最终调用到了performTraversals 中,这个方法巨长,涉及到了 onMeasure/onLayout/onDraw 等重要方法的起源:

private void performTraversals() {
    ...
      // 这里最终会触发 view 的 onMeasure
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      performLayout(lp, mWidth, mHeight);
      performDraw();
    ...
    mIsInTraversal = false;
  }

看到上面就是对应着 View 的绘制流程了,继续看 performDraw 的实现:

private void performDraw() {
    ...
    try {
      draw(fullRedrawNeeded);
    } finally {
      mIsDrawing = false;
      Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ...
  }
  private void draw(boolean fullRedrawNeeded) {
    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
      return;
    }
  }
  private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
      boolean scalingRequired, Rect dirty) {
      ...
      try {
        canvas.translate(-xoff, -yoff);
        if (mTranslator != null) {
          mTranslator.translateCanvas(canvas);
        }
        canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
        attachInfo.mSetIgnoreDirtyState = false;
        // 最终调用了 View 的 draw 方法了
        mView.draw(canvas);
       } finally {
        ...
      }
    return true;
  }

看到终于调用到 View 的 draw 方法来了,继续看下 ViewGroup 和 View 在这方法中的处理方式:

// View 中的 draw 方法:
  public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
        (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *   1. Draw the background
     *   2. If necessary, save the canvas' layers to prepare for fading
     *   3. Draw view's content
     *   4. Draw children
     *   5. If necessary, draw the fading edges and restore layers
     *   6. Draw decorations (scrollbars for instance)
     */
    // Step 1, draw the background, if needed
    int saveCount;

    if (!dirtyOpaque) {
      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
      if (!dirtyOpaque) 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;
    }

    /*
     * Here we do the full fledged routine...
     * (this is an uncommon case where speed matters less,
     * this is why we repeat some of the tests that have been
     * done above)
     */
    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 solidColor = getSolidColor();
    if (solidColor == 0) {
      final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

      if (drawTop) {
        canvas.saveLayer(left, top, right, top + length, null, flags);
      }

      if (drawBottom) {
        canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
      }

      if (drawLeft) {
        canvas.saveLayer(left, top, left + length, bottom, null, flags);
      }

      if (drawRight) {
        canvas.saveLayer(right - length, top, right, bottom, null, flags);
      }
    } else {
      scrollabilityCache.setFadeColor(solidColor);
    }
    // Step 3, draw the content
    if (!dirtyOpaque) 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;
    if (drawTop) {
      matrix.setScale(1, fadeHeight * topFadeStrength);
      matrix.postTranslate(left, top);
      fade.setLocalMatrix(matrix);
      p.setShader(fade);
      canvas.drawRect(left, top, right, top + length, p);
    }
    if (drawBottom) {
      matrix.setScale(1, fadeHeight * bottomFadeStrength);
      matrix.postRotate(180);
      matrix.postTranslate(left, bottom);
      fade.setLocalMatrix(matrix);
      p.setShader(fade);
      canvas.drawRect(left, bottom - length, right, bottom, p);
    }
    if (drawLeft) {
      matrix.setScale(1, fadeHeight * leftFadeStrength);
      matrix.postRotate(-90);
      matrix.postTranslate(left, top);
      fade.setLocalMatrix(matrix);
      p.setShader(fade);
      canvas.drawRect(left, top, left + length, bottom, p);
    }
    if (drawRight) {
      matrix.setScale(1, fadeHeight * rightFadeStrength);
      matrix.postRotate(90);
      matrix.postTranslate(right, top);
      fade.setLocalMatrix(matrix);
      p.setShader(fade);
      canvas.drawRect(right - length, top, right, bottom, 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);
  }

这个方法的大体意思是这样的:

@Override
  public void draw(Canvas canvas) {
    ...
    drawBackground(canvas); // 绘制背景
    onDraw(canvas); // 调用自己的 onDraw 方法来绘制内容
    dispatchDraw(canvas); // 分发绘制
    onDrawForeground(canvas); // 绘制前景
    ...
  }

上面几个方法中,只有 dispatchDraw 涉及到分发绘制,其他的都是对自身的绘制,所以继续看 dispatchDraw

// View 中的实现,是个空方法,也就是 View 没有孩子,不需要什么分发
  protected void dispatchDraw(Canvas canvas) {

  }

View 中的实现,是个空方法,也就是 View 没有孩子,不需要什么分发。那么看下 ViewGroup 中是怎么分发的:

@Override
  protected void dispatchDraw(Canvas canvas) {
    ...
    while (transientIndex >= 0) {
      // there may be additional transient views after the normal views
      final View transientChild = mTransientViews.get(transientIndex);
      if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
          transientChild.getAnimation() != null) {
        // 看到这里,会去绘制 子View
        more |= drawChild(canvas, transientChild, drawingTime);
      }
      transientIndex++;
      if (transientIndex >= transientCount) {
        break;
      }
    }
    ...
  }
  protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    // 又调回了 View 的 draw 来了
    return child.draw(canvas, this, drawingTime);
  }

上面又调会了 View 的 draw 来了,如此递归调用下去,直到遍历完所有的 VIew 。

4. 总结

1 invalidate 和 postInvalidate 的关系:

postInvalidate 最终通过 Handler 切换到主线程,调用 invalidate

2 能否在子线程中更新 UI ?

只要在校验 UI 线程前,子线程是可以更新 UI 的,也就是 Activity 的 onResume 方法前。因为在 onResume 中创建了 ViewRootImpl。

3 invalidate 源码

invalidate 会先找到父类去走绘制流程,最终遍历所有相关联的 View ,触发它们的 onDraw 方法进行绘制

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 本文向大家介绍浅谈springmvc的DispatcherServlet分析,包括了浅谈springmvc的DispatcherServlet分析的使用技巧和注意事项,需要的朋友参考一下 本文介绍了springmvc的DispatcherServlet,分享给大家,具体如下: 一、程序 (一)web.xml文件中的内容 (二)src/main/resource/springmvc/spring-m

  • 本文向大家介绍浅谈JavaScript 框架分类,包括了浅谈JavaScript 框架分类的使用技巧和注意事项,需要的朋友参考一下 如果是从内部架构与理念划分,目前JavaScript框架可以划分为5类。 第1种 出现的是以命名空间为导向的类库或框架,如创建一个数组用new Array(),生成一个对象用new Object(),完全的Java风格,因此我们就可以以某一对象为根,不断为它添加对象属

  • 本文向大家介绍浅谈jQuery构造函数分析,包括了浅谈jQuery构造函数分析的使用技巧和注意事项,需要的朋友参考一下 在我的上一篇文章里面 阐述了jQuery的大致框架,知道了所有代码都是写在了一个自调用匿名函数里面,并且传入了window对象,源码是这样的: 我们通过alert(jquery) 知道它是一个对象,那么这个对象是怎么构造出来的呢?我们使用$(document)类似的写法获取元素,

  • 本文向大家介绍浅谈MySQL索引优化分析,包括了浅谈MySQL索引优化分析的使用技巧和注意事项,需要的朋友参考一下 为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字段的意义。助你了解索引,分析索引,使用索引,从而写出更高性能的sql语句。还在等啥子?撸起袖子就是干! 案例

  • 大家都知道,在286以上的计算机中,一般都有一个CMOS RAM电路,它用于关机以后继续存放日期、时间、内存设置、软硬盘类型及其他许多有用的设置信息。CMOS即互补金属氧化物半导体,它的设置、应用和管理是保证系统正常工作的关键,下面就介绍一些有关CMOS的基本概念和应用。 ROM BIOS和CMOS RAM芯片 ROM BIOS是固化在ROM中的BIOS(Basic Input/Output Sy

  • 在 JavaScript 中,作用域、上下文、闭包、函数等算是精华中的精华了。对于初级 JSer 来说,是进阶必备。对于前端攻城师来说,只有静下心来,理解了这些精华,才能写出优雅的代码。