05.源码阅读(View的invalidate,postInvalidate和requestLayout)

简介: 关键词:View ViewGroup ViewRootImplinvalidate是如何刷新view的?View/** * 必须可见才能刷新,运行于UI线程 * Invalidate the whole view.

关键词:View ViewGroup ViewRootImpl

invalidate是如何刷新view的?

View

/**
     * 必须可见才能刷新,运行于UI线程
     * Invalidate the whole view. If the view is visible,
     * This must be called from a UI thread. To call from a non-UI thread, call
     * {@link #postInvalidate()}.
     */
    public void invalidate() {
        invalidate(true);
    }
/**
     * invalidateCache设置为true,会刷新所有的view,包括大小没有改变的view
     * 设置为false,只会刷新需要被刷新的view,例如大小发生改变
     */
    public void invalidate(boolean invalidateCache) {
        //mRight - mLeft 该view占据的宽度范围,mBottom - mTop高度范围
        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;
        }
        //跳过刷新的条件
        //View不是可见的 && 存在动画对象 && 父视图不是ViewGroup或者不是过渡态
        if (skipInvalidate()) {
            return;
        }
        //如下条件才可以重绘:正在动画或者View大小不是0 || 需要完整绘制绘制并且绘制缓存可用 || 未重绘过 || 透明度和上次比较有了变化
        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)) {
            ......
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }
            ......
        }
    }

其中的这个ViewParent p,ViewParent 是一个接口,它的子类,ViewGroup,ViewRootImpl,在两个字类中都有invalidateChild方法,如下

ViewGroup中

@Deprecated
    @Override
    public final void invalidateChild(View child, final Rect dirty) {

            ......

            do {
                
                ......

                parent = parent.invalidateChildInParent(location, dirty);

                ......
                
            } while (parent != null);
        }
    }

/**
     * Don't call or override this method. It is used for the implementation of
     * the view hierarchy.
     *
     * This implementation returns null if this ViewGroup does not have a parent,
     * if this ViewGroup is already fully invalidated or if the dirty rectangle
     * does not intersect with this ViewGroup's bounds.
     *
     * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
     * draw state in descendants.
     */
    @Deprecated
    @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;
    }

可以看出,当调用了子view的invalidate方法后,刷新是逐层往上进行的,通过一个do while循环不断的获取上层的父布局,该循环的作用主要是不断向上回溯父容器,求得父容器和子View需要重绘的区域的并集(dirty)。调用父布局的invalidateChildInParent方法,直到最上层没有parent为止,最后一次调用会进入到ViewRootImpl的invalidateChildInParent方法。总的来说事件层层向上传递,直到DecorView,而DecorView又会传递给ViewRootImpl,也即是说子View的invalidate事件,最终会被ViewRootImpl接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有ViewRootImpl能够处理invalidate事件,在ViewGroup中我们是看不到有效的处理方法的

ViewRootImpl中

@Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }

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

可以看到不论是当dirty==null时走的invalidate()方法还是invalidateRectOnScreen方法,最终都会调用scheduleTraversals()方法

scheduleTraversals

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

开启了一个Runnable线程

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

doTraversal

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

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

            performTraversals();

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

performTraversals

private void performTraversals() {
    ......
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
   
    performLayout(lp, mWidth, mHeight);
    
    .....
    performDraw();
    ......
}

到了这里会回调onMeasure onLayout onDraw方法,现在我们先关注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) {

        // Draw with software renderer.
        //最终还是canvas来绘制
        final Canvas canvas;
        
        ......
        //这个View是之前循环遍历出来的最外层的那个view,调用draw方法,会不断的向下绘制,在View的源码中可以看到在绘制的同时,还在向下分发draw事件:dispatchDraw(canvas);
                mView.draw(canvas);

        ......
    }

postInvalidate是如何刷新view的?

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

@Override
public void handleMessage(Message msg) {
      switch (msg.what) {
      case MSG_INVALIDATE:
         //调用了View的invalidate
         ((View) msg.obj).invalidate();
              break;
          }
      }
      ......
}

requestLayout是如何刷新view的?

@CallSuper
    public void requestLayout() {
        ......
        //判断如果当前View树是否正在布局流程,返回
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting       it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        //这两个标记很关键,是区分measure layout draw方法是否执行的标记
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        ......
    }

mParent.requestLayout()方法:调用mParent.requestLayout方法,这个十分重要,因为这里是向父容器请求布局,即调用父容器的requestLayout方法,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到DecorView,即根View,而根View又会传递给ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,在这里,只有ViewRootImpl能够处理requestLayout事件

在ViewRootImpl中

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

到这里,基本就和invalidate方法源码走入了相同的处理方法中,不同的是,根据标记的不同而进行不同的处理

问题:invalidate和requestLaout区别何在?

通过上边的源码分析,我们已经知道,invalidate和requestLayout最终殊途同归,汇集到了一个方法中,就是scheduleTraversals,那么它们逻辑有何不同呢,其实区别在这里

private void performTraversals() {
    ......
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
   
    performLayout(lp, mWidth, mHeight);
    
    .....
    performDraw();
    ......
}

当源码走到这个方法中后,会分别去调用View的measure layout 和draw方法,但是其实这并不能说明,onMeasure onLayout onDraw方法都会被回调,这时,之前设置的标记就起作用了

我们来看measure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    //可以看到PFLAG_FORCE_LAYOUT是requestLayout时设置的标记,所以requestLayout会最终执行onMeasure,invalidate则相反
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    ......
    if (forceLayout || needsLayout) {
        ......
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        .....
        //又设置了一个标记,留心,这个很有用
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
}

layout

public void layout(int l, int t, int r, int b) {
        ......
        //PFLAG_LAYOUT_REQUIRED是measure中回调了onMeasure之后设置的标记,这里用到了,也就是requestLayout代码走到onMeasure之后还会走到这里的onLayout
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

           ......
        }
    }

draw,两者都会回调

@CallSuper
    public void draw(Canvas canvas) {
        ......
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            //回调onDraw方法
            if (!dirtyOpaque) onDraw(canvas);
        }
  }

所以二者区别在于requestLayout 会回调三个,invalidate只会回调onDraw

相关文章
|
4月前
|
Android开发
Android面试题之View的invalidate方法和postInvalidate方法有什么区别
本文探讨了Android自定义View中`invalidate()`和`postInvalidate()`的区别。`invalidate()`在UI线程中刷新View,而`postInvalidate()`用于非UI线程,通过消息机制切换到UI线程执行`invalidate()`。源码分析显示,`postInvalidate()`最终调用`ViewRootImpl`的`dispatchInvalidateDelayed`,通过Handler发送消息到UI线程执行刷新。
58 1
|
Android开发
ViewPager源码分析(2):滑动及冲突处理
我的简书同步发布:ViewPager源码分析(2):滑动及冲突处理 转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001】 上一篇介绍了ViewPager的onMeasure和onLayout两个方法,这是自定义View最基本的两个函数。但是我们的ViewPager有个需求就是滑动,接下来我们一起去学习ViewPager在滑动方面做了哪些工作,以及ViewPager如何处理与子View之间的滑动冲突。由于ViewPager的子View有Decor View还有普通的子View,而本篇文章讲的主要是普通子View,因此,不再去刻意区
ViewPager源码分析(2):滑动及冲突处理
|
Android开发 容器
深入了解View的滑动冲突
在《与滑动冲突的首次邂逅》一文中,笔者举了一个开发过程中出现的一个简单的滑动冲突问题,带大家直观的了解何为滑动冲突,并且使用了内部拦截法(内部解决法)来解决了这个滑动冲突。
深入了解View的滑动冲突
|
Android开发 容器
View工作原理分析1 - 初识ViewRoot和 DecorView
以下相关资料均来自 Android艺术探索,部分内容加入了一些我个人的理解。
144 0
|
缓存
ViewPager懒加载的实现,理解setUserVisibleHint,而不只是会用就好
Viewpager默认会缓存临近操作的两个页面,也就是至少会缓存一个页面。
204 0
ViewPager懒加载的实现,理解setUserVisibleHint,而不只是会用就好
|
前端开发
View#invalidate是如何调用当前View#onDraw方法的?
View#invalidate是如何调用当前View#onDraw方法的?
ViewPager源码分析(1):onMeasure、onLayout
我的简书同步发布:ViewPager源码分析(1):onMeasure、onLayout 转载请注明出处:【huachao1001的专栏:http://blog.csdn.net/huachao1001】 在网上看了一些面经,感觉ViewPager被问到的概率还是蛮大的,于是决定去好好研究ViewPager源码,一步一步去琢磨ViewPager的实现,并写到博文里来~。 我们知道,ViewPager是一个ViewGroup,而我们平时自己自定义ViewGroup时,除了至少写两个构造函数以外,onMeasure和onLayout这两个函数基本上是必须要去写的。今天先把onMeasure和
|
缓存 Android开发 容器
ViewPager刷新问题原理分析及解决方案(FragmentPagerAdapter+FragementStatePagerAdapter)
ViewPager刷新问题原理分析及解决方案(FragmentPagerAdapter+FragementStatePagerAdapter)
580 0
ViewPager刷新问题原理分析及解决方案(FragmentPagerAdapter+FragementStatePagerAdapter)
|
前端开发 开发者
一道面试题:ViewPager中的Framgent如何实现懒加载?
setUserVisiblity已被废弃,推荐使用 setMaxLifecycle 处理 Fragment 在 ViewPager 中的懒加载
422 0
一道面试题:ViewPager中的Framgent如何实现懒加载?