View的invalidate()流程​

QuibblerQuibbler 2022-01-21 约 22 分钟 2542 次阅读

View的invalidate()流程

自定义View的时候常用到invalidate()方法,为了刷新View以重新绘制。

/**
     * 使整个View视图无效。 如果视图可见,onDraw(Canvas) 将在将来的某个时间点被调用。
     * <p>
     * 必须从 UI 线程调用。 要从非 UI 线程调用,用 postInvalidate()
     */
    public void invalidate() {
        invalidate(true);
    }

和requestLayout()不同的是,调用invalidate()后只会回调调用View的draw()方法;而调用requestLayout()则会从上到下重新测量、布局一遍。 

应根据实际情况选择使用invalidate()还是requestLayout(),如果只需要刷新View,View的布局没有发生改变,仅需要用invalidate()即可;如果View的大小、布局、LayoutParams发生改变,则应当用requestLayout()请求重新测量布局。

有时还会看到这两伙伴常组合在一起使用,比如在ListView中:

public void setDivider(@Nullable Drawable divider) {
        ...
        requestLayout();    //先请求重新布局
        invalidate();       //再重绘View
    }

1、invalidate()方法流程

从invalidate()方法调用默认invalidateCache参数true,使此View的绘图缓存也失效。当然这也是默认的选择,因为带参数的invalidate(boolean invalidateCache)是hide方法。

/**
     * @hide
     */
    public void invalidate(boolean invalidateCache) {
        //传入的参数是当前View的上下左右区域
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

调用内部的invalidateInternal()方法,在该方法中有如下几个操作:重置View缓存,设置PFLAG_INVALIDATED标志位、向上传递invalidate;

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {
        //mGhostView是Overlay中的View,如果存在mGhostView,那么只绘制mGhostView视图
        if (mGhostView != null) {
            mGhostView.invalidate(true);
            return;
        }
        
        //如果View不可见且未运行动画,则直接跳过invalidate
        if (skipInvalidate()) {
            return;
        }
        
        // 重置缓存
        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
        mContentCaptureSessionCached = false;
        
        //View如果没有任何变化,那么就不需要重绘
        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;
            }
            
            //设置PFLAG_DIRTY标记位
            mPrivateFlags |= PFLAG_DIRTY;
            
            //invalidateCache默认为true,添加PFLAG_INVALIDATED标志位使缓存失效
            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;          //置为1,设置
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; //置为0,清除
            }
            
            final AttachInfo ai = mAttachInfo;
            //通过mParent,即当前View的父View,将需要重新绘制的矩形区域传递到父View
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                //设置View的边界
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }
            
            // 如果此View有Background,则使用View的Outline来裁剪View的内容
            if (mBackground != null && mBackground.isProjected()) {
                final View receiver = getProjectionReceiver();
                if (receiver != null) {
                    receiver.damageInParent();
                }
            }
        }
    }

随后进入ViewParent的invalidateChild()方法,该方法在ViewGroup中实现。此方法代码比较多,选择性忽视大部分代码,只看关键代码:

/**
     * Don't call or override this method. It is used for the implementation of
     * the view hierarchy.
     */
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        
        if (attachInfo != null && attachInfo.mHardwareAccelerated) {
            // 启用硬件加速快速刷新
            onDescendantInvalidated(child, child);
            return;
        }
        
        //ViewParent就是当前ViewGroup
        ViewParent parent = this;
        ...
            do {
                ...
                //然后沿着ViewParent链向上,直到ViewRootImpl
                parent = parent.invalidateChildInParent(location, dirty);
                ...
            } while (parent != null);
        }
    }

在invalidateChild()方法中,通过invalidateChildInParent()层层向上传递,该方法在ViewGroup中的实现如下:

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        //判断标志位,这里的两个标志位正是在invalidateInternal()方法中设置的
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
            // DRAWN 或 DRAWING_CACHE_VALID,并且将要重绘的dirty区域和父容器需要绘制的区域进行union合并
            
            ...
            
            //中间忽略了计算过程,返回当前父容器
            return mParent;
        }
        
        //如果没有标志位,则返回null,直接终止invalidateChild()中的do-while循环
        return null;
    }

不断沿着ViewParent链向上层层传递,最终到了ViewRootImpl,类似View的requestLayout()流程。invalidateChildInParent()方法在ViewRootImpl中的实现如下:

@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        //ViewRootImpl中的UI操作都会判断是否在UI线程
        checkThread();
        ...
        
        //在屏幕上使指定的dirty区域失效
        invalidateRectOnScreen(dirty);
        
        //ViewRootImpl中的方法返回null,invalidateChild()中的do-while的循环一定会终止
        return null;
    }

invalidateRectOnScreen(Rect dirty)的方法定义如下:将View需要绘制的dirty区域添加到当前需要绘制的区域中,再与应用可见区域相交,最后开启遍历。

private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;
        // 将需要重绘的矩形区域,放到当前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();
        }
    }

在最后走到了scheduleTraversals(),开始View树的重绘流程(因为invalidate()没有添加measure和layout的标记位,因此measure、layout流程不会执行,draw流程也只对需要重绘的视图进行绘制)。在重新绘制的时候,只有调用invalidate()的View有PFLAG_INVALIDATED标志位会重绘:

private void updateViewTreeDisplayList(View view) {
        view.mPrivateFlags |= View.PFLAG_DRAWN;
        
        //判断View是否有PFLAG_INVALIDATED标志
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        
        //更新View的视图,依次调用draw()、onDraw()
        view.updateDisplayListIfDirty();
        view.mRecreateDisplayList = false;
    }

看到这里相信对后面的已经很熟悉了,详见View的绘制流程一文。invalidate()的流程图如下:

2、postInvalidate()方法流程

invalidate()方法方法只能在主线程调用,如果在子线程中调用,可以使用postInvalidate()方法:

public void postInvalidate() {
        //立即更新 0延迟
        postInvalidateDelayed(0);
    }

内部调用另一个postInvalidateDelayed(long delayMilliseconds)方法,此方法可以设置invalidate延迟时间:

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;
        
        //只有当前View附着在Window上,才会走Invalidate流程
        if (attachInfo != null) {
            attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
        }
    }

通过View.AttachInfo中携带的ViewRootImpl(当View附着时其mAttachInfo会被赋值,来自ViewRootImpl初始化时创建的mAttachInfo,本篇不展开),调用其dispatchInvalidateDelayed(View view, long delayMilliseconds)方法:

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        //将当前View作为Message中携带的obj对象
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        
        //交给ViewRootImpl中的Handler处理MSG_INVALIDATE消息
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }

mHandler是定义在ViewRootImpl中的ViewRootHandler,其handleMessage(Message msg)实现如下:

final class ViewRootHandler extends Handler {
        ...
        
        @Override
        public void handleMessage(Message msg) {
            handleMessageImpl(msg);
        }
        
        private void handleMessageImpl(Message msg) {
            switch (msg.what) {
                case MSG_INVALIDATE:
                    //在UI线程中调用View的invalidate()
                    ((View) msg.obj).invalidate();
                    break;
                ...
            }
        }
        
    }

最终还是回到UI线程中执行View的invalidate()方法。流程图如下


相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w