源码分析-Android-View-invalidate 绘制流程

1,391 阅读1分钟

背景

Invalidate()AndroidView 的方法,通常我们使用它来完成UI的刷新,

作用

如果这个 View 可见那么 onDraw() 方法将在未来某个时间点被调用。

问题

invalidate() 会触发那些 view 的重绘,invalidate() 绘制流程是如何实现的?

我们带着问题来从源码开始分析:

源码分析

一、View 与 ViewGroup 的层级

AndroidView 是以树形结构组织的,下图相信大家都不陌生,那么我们调用红色Viewinvalidate() 会发生什么?

image.png

1.1 View.invalidate()

方法入口:

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

参数:

  • l,t,r,b 是 View 的大小
  • invalidateCache: 设置 View 的缓存是否失效,通常情况下是 ture, 当 View 的大小改变时为 false
  • fullInvalidate: 默认为 true
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
        ...
        
        final AttachInfo ai = mAttachInfo;
        // 改 View 的父布局
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            // 记录需要绘制的范围 damge ,该区域为 View 尺寸
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            // 调用父布局的 invalidateChild()
            p.invalidateChild(this, damage);
        }

       ...
}

说明:View 需要绘制大小 Rect 告诉父ViewGroup, 并调用父 ViewGroupinvalidateChild()

1.2 ViewGroup.invalidateChild

public final void invalidateChild(View child, final Rect dirty) {
    // 如果是硬件加速,走改分支
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        onDescendantInvalidated(child, child);
        return;
    }
    
    // 软件绘制
    ViewParent parent = this;
    if (attachInfo != null) {
        ...
        // 这个循环会一直找到父布局的 DecordView invalidateChildInParent()
        do {
        ...
            // 标记 View
            if (view != null) {
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
                }
            }
          
            parent = parent.invalidateChildInParent(location, dirty);
           ...
        } while (parent != null);
    }
}

绘制分为两个分支:

  • 硬件加速绘制
  • 软件绘制 硬件加速绘制不做介绍,主要分析软件绘制

软件绘制会循环一直到根的 DecorView 中,而 DecorView 是由 ViewRootImp 管理,并维护 mPrivateFlags

mPrivateFlags:计算需要刷新的View

1.3 ViewRootImp.invalidateChildInParent()

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

        invalidateRectOnScreen(dirty);

        return null;
    }

在根布局 ViewRootImp 会处理我们传入的 Rect dirty 区域。

1.4 ViewRootImp.invalidateRectOnScreen()

  private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;

        // 通过我们传入的区域,计算需要更新的区域
        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
        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();
        }
    }

通过我们 dirty 的区域,计算需要更新的区域,然后调用 scheduleTraversals()

1.5 ViewRootImp.scheduleTraversals()

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

通过 Handler 开启同步屏障, Post 一个 Callback,接下来我们来看这个 Callback 的实现。

1.6 ViewRootImp.mTraversalRunnable

   void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

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

主要记录绘制时间,并调用 performTraversals() 开始绘制

1.7 核心函数 ViewRootImp.performTraversals()

void performTraversals() {
    if (layoutRequested) {
            // Clear this now, so that if anything requests a layout in the
            // rest of this function we will catch it and re-run a full
            // layout pass.
            mLayoutRequested = false;
        }
      ...
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      ...
      performLayout(lp, mWidth, mHeight);
      ...
      performDraw();
      ...
}

依次执行 performMeasure(),performLayout(),performDraw()

mLayoutRequested: 默认为 false,意味着只会执行 onDraw(), 不会执行 onMeasure()

1.8 performMeasure()

  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

调用 View.measure()

1.9 View.measure()

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

        if (forceLayout || needsLayout) {
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                ...
            }
    
    }

view.measure() 会调用我们重写的 onMeasure()

View.layout()View.draw() 方法与 View.measure() 方法执行流程类似不在分析

总结:

image.png

invalidate() 整体流程:

AndroidUI 刷新的逻辑都是按照以上树形结构来运行的,从子 View 把要做的事情传给根布局,再由根布局分布整个 View Tree , 导致整个 View Tree 进行刷新。

invalidate() 性能考虑:

  1. View 将更新逻辑传递给 ViewRootImp 过程中,通过 Rect 计算需要更新View,并用 mPrivateFlags 标记出来。
  2. View 从上往下执行 onMeasure()、onLayout()、onDraw() 时通过 mPrivateFlags 判断是否刷新达到优化的目的

invalidate只是调用 performDraw()

onMeasure()、onLayout()、onDraw() 这三个函数不一定都执行,由 mLayoutRequested 默认是 false 仅会执行 onDraw()

如果发现 onMesure()、onLayout() 无效时候,通常我们会调用 requestLayout(), 本质上它是将 mLayoutRequested 设置为 true