微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Android-View-invalidate 绘制流程

转载地址:https://juejin.cn/post/7100121390090551332

背景

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

@L_404_1@作用

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

问题

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

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

源码分析

一、View 与 ViewGroup 的层级

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

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,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,(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() 方法执行流程类似不在分析

总结:

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐