目录介绍

  • 01.invalidate,requestLayout,postInvalidate区别
  • 02.invalidate深入分析
  • 03.postInvalidate深入分析
  • 04.requestLayout深入分析
  • 05.ViewRootImpl作用分析
  • 06.这几个方法总结

好消息

01.requestLayout、invalidate与postInvalidate作用与区别

  • invalidate() postInvalidate()

    • 共同点:都是调用onDraw()方法,然后去达到重绘view的目的
    • 区别:invalidate()用于主线程,postInvalidate()用于子线程【通过handler发送消息,在handleMessage中((View) msg.obj).invalidate(),】
  • requestLayout()
    • 也可以达到重绘view的目的,但是与前两者不同,它会先调用onLayout()重新排版,再调用ondraw()方法。
    • 当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view(父类的视图)重新调用他的onMeasure、onLayout来重新设置自己位置。特别是当view的layoutparameter发生改变,并且它的值还没能应用到view上时,这时候适合调用这个方法requestLayout()。 requestLayout调用onMeasure和onLayout,不一定调用onDraw

02.invalidate深入分析

  • 看看invalidate源码,如下所示

    • invalidateCache为true表示全部重绘。View的invalidate方法最后调用的是invalidateInternal方法,invalidateInternal方法中的重点逻辑在源码上添加注释呢。
    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);
    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();
    }
    }
    }
    }
  • 看看ViewGroup中的invalidateChild方法
    • 在ViewGroup的invalidateChild方法中有一个循环,循环里面会一直调用父布局的invalidateChildInParent方法,而View和ViewGroup的最终父布局都是ViewRootImpl。
    @UiThread
    public abstract class ViewGroup extends View implements ViewParent, ViewManager { @Override
    public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
    //这是一个从当前的布局View向上不断遍历当前布局View的父布局,最后遍历到ViewRootImpl的循环
    do {
    View view = null;
    if (parent instanceof View) {
    view = (View) parent;
    } //这里调用的是父布局的invalidateChildInParent方法
    parent = parent.invalidateChildInParent(location, dirty);
    } while (parent != null);
    }
    } @Override
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
    (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
    if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
    FLAG_OPTIMIZE_INVALIDATE) {
    ...
    //这里也是一些计算绘制区域的内容
    return mParent;
    } else {
    mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
    //这里也是一些计算绘制区域的内容
    return mParent;
    }
    }
    return null;
    }
    }
  • View中的invalidateChild方法和ViewGroup中的invalidateChildInParent方法最后殊途同归,都会调用到ViewRootImpl中的方法
    • 可以看到在ViewRootImpl中最后都会调用scheduleTraversals方法进行绘制。按照对于View的绘制过程的理解,View的绘制流程是从ViewRootImpl的performTraversals方法开始的
    public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
    
        //如果View没有父布局,那invalidateInternal方法就会调用这个方法
    @Override
    public void invalidateChild(View child, Rect dirty) {
    invalidateChildInParent(null, dirty);
    } //ViewGroup的invalidateChild方法最后会调用到这里
    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    //如果dirty为null就表示要重绘当前ViewRootImpl指示的整个区域
    if (dirty == null) {
    invalidate();
    return null;
    //如果dirty为empty则表示经过计算需要重绘的区域不需要绘制
    } else if (dirty.isEmpty() && !mIsAnimating) {
    return null;
    }
    return null;
    } private void invalidateRectOnScreen(Rect dirty) {
    final Rect localDirty = mDirty;
    ...
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
    //调用scheduleTraversals方法进行绘制
    scheduleTraversals();
    }
    } //绘制整个ViewRootImpl区域
    void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
    //调用scheduleTraversals方法进行绘制
    scheduleTraversals();
    }
    }
    }
  • 下面我们来看看ViewRootImpl中的scheduleTraversals方法
    • 看到scheduleTraversals方法中调用了mChoreographer.postCallback方法
    • Choreoprapher类的作用是编排输入事件、动画事件和绘制事件的执行,它的postCallback方法的作用就是将要执行的事件放入内部的一个队列中,最后会执行传入的Runnable,这里也就是mTraversalRunnable。
    void scheduleTraversals() {
    if (!mTraversalScheduled) {
    mTraversalScheduled = true;
    mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
    mChoreographer.postCallback(
    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    if (!mUnbufferedInputDispatch) {
    scheduleConsumeBatchedInput();
    }
    notifyRendererOfFramePending();
    pokeDrawLockIfNeeded();
    }
    }
  • 来看看TraversalRunnable这个类做了什么?
    • 可以看到最终调用了performTraversals()方法进行绘制
    final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
    doTraversal();
    }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void doTraversal() {
    if (mTraversalScheduled) {
    mTraversalScheduled = false;
    mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) {
    Debug.startMethodTracing("ViewAncestor");
    } performTraversals(); if (mProfile) {
    Debug.stopMethodTracing();
    mProfile = false;
    }
    }
    }
  • 大概总结一下
    • invalidate方法最终调用的是ViewRootImpl中的performTraversals来重新绘制View
    • 在自定义View时,当需要刷新View时,如果是在UI线程中,那就直接调用invalidate方法,如果是在非UI线程中,那就通过postInvalidate方法来刷新View

03.postInvalidate深入分析

  • 先来看看View中的postInvalidate方法

    @UiThread
    public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource { ... public void postInvalidate() {
    postInvalidateDelayed(0);
    } public void postInvalidate(int left, int top, int right, int bottom) {
    postInvalidateDelayed(0, left, top, right, bottom);
    } public void postInvalidateDelayed(long delayMilliseconds) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
    attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
    }
    ...
    }
  • 可以看到,postInvalidate方法最后调用了ViewRootImpl的dispatchInvalidateDelayed方法
    • ViewRootImpl中的dispatchInvalidateDelayed方法就是像ViewRootHandler发送了一个MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一个内部Handler类
    //发送消息
    //更多内容:https://github.com/yangchong211
    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 String getMessageName(Message message) {
    switch (message.what) {
    case MSG_INVALIDATE:
    return "MSG_INVALIDATE";
    }
    return super.getMessageName(message);
    } @Override
    public void handleMessage(Message msg) {
    switch (msg.what) {
    case MSG_INVALIDATE:
    ((View) msg.obj).invalidate();
    break;
    }
    }
    }
  • 大概总结一下
    • postInvalidate方法调用了ViewRootImpl中的dispatchInvalidateDelayed方法向ViewRootImpl中的ViewRootHandler发送一个消息,最后调用的还是View的invalidate方法。
    • 因为ViewRootImpl是在UI线程的,所以postInvalidate方法的作用就是将非UI线程的刷新操作切换到UI线程,以便在UI线程中调用invalidate方法刷新View。所以如果我们自定义的View本身就是在UI线程,没有用到多线程的话,直接用invalidate方法来刷新View就可以了。而我们平时自定义View基本上都没有开起其他线程,所以这就是我们很少接触postInvalidate方法的原因。
    • 一句话总结postInvalidate方法的作用就是:实现了消息机制,可以使我们在非UI线程也能调用刷新View的方法。

04.requestLayout深入分析

  • 源码如下所示

    • 在View的requestLayout方法中,首先会设置View的标记位,PFLAG_FORCE_LAYOUT表示当前View要进行重新布局,PFLAG_INVALIDATED表示要进行重新绘制。
    • requestLayout方法中会一层层向上调用父布局的requestLayout方法,设置PFLAG_FORCE_LAYOUT标记,最终调用的是ViewRootImpl中的requestLayout方法。
    //View.class
    @CallSuper
    public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear(); 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;
    } //设置PFLAG_FORCE_LAYOUT标记位,这样就会导致重新测量和布局
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    //设置PFLAG_INVALIDATED就会进行重新绘制
    mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) {
    //不断调用上层View的requestLayout方法
    mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
    mAttachInfo.mViewRequestingLayout = null;
    }
    }
  • 然后看看ViewRootImpl中的requestLayout方法
    • 可以看到ViewRootImpl中的requestLayout方法中会调用scheduleTraversals方法,scheduleTraversals方法最后会调用performTraversals方法开始执行View的三大流程,会分别调用View的measure、layout、draw方法。
    @Override
    public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
    checkThread();
    mLayoutRequested = true;
    scheduleTraversals();
    }
    }
  • 然后再看看measure测量方法
    • 由于requestLayout方法设置了PFLAG_FORCE_LAYOUT标记位,所以measure方法就会调用onMeasure方法对View进行重新测量。在measure方法中最后设置了PFLAG_LAYOUT_REQUIRED标记位,这样在layout方法中就会执行onLayout方法进行布局流程。
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    if (forceLayout || needsLayout) {
    // first clears the measured dimension flag
    mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
    //调用onMeasure方法
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    } else {
    long value = mMeasureCache.valueAt(cacheIndex);
    setMeasuredDimensionRaw((int) (value >> 32), (int) value);
    mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    } //设置PFLAG_LAYOUT_REQUIRED标记位,用于layout方法
    mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
    }
  • 再然后看看layout方法
    • 由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以在layout方法中onLayout方法会被调用执行布局流程。最后清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED标记位。
    public void layout(int l, int t, int r, int b) {
    //由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以会进入调用onLayout方法进行布局流程
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) {
    if(mRoundScrollbarRenderer == null) {
    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
    }
    } else {
    mRoundScrollbarRenderer = null;
    } //取消PFLAG_LAYOUT_REQUIRED标记位
    mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnLayoutChangeListeners != null) {
    ArrayList<OnLayoutChangeListener> listenersCopy =
    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
    int numListeners = listenersCopy.size();
    for (int i = 0; i < numListeners; ++i) {
    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
    }
    }
    } //取消PFLAG_FORCE_LAYOUT标记位
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

05.ViewRootImpl作用分析

  • 链接WindowManager和DecorView的纽带,另外View的绘制也是通过ViewRootImpl来完成的。

    • 它的主要作用我的总结为如下:
    • A:链接WindowManager和DecorView的纽带,更广一点可以说是Window和View之间的纽带。
    • B:完成View的绘制过程,包括measure、layout、draw过程。
    • C:向DecorView分发收到的用户发起的event事件,如按键,触屏等事件。

06.这几个方法总结

  • requestLayout方法会标记PFLAG_FORCE_LAYOUT,然后一层层往上调用父布局的requestLayout方法并标记PFLAG_FORCE_LAYOUT,最后调用ViewRootImpl中的requestLayout方法开始View的三大流程,然后被标记的View就会进行测量、布局和绘制流程,调用的方法为onMeasure、onLayout和onDraw。
  • invalidate方法我们分析过,它的过程和requestLayout方法方法很像,但是invalidate方法没有标记PFLAG_FORCE_LAYOUT,所以不会执行测量和布局流程,而只是对需要重绘的View进行重绘,也就是只会调用onDraw方法,不会调用onMeasure和onLayout方法。

其他介绍

01.关于博客汇总链接

02.关于我的博客

View之invalidate,requestLayout,postInvalidate的更多相关文章

  1. Android view中的requestLayout和invalidate方法

    Android view中的requestLayout和invalidate方法 requestLayout:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent v ...

  2. Android开发 View的UI刷新Invalidate和postInvalidate

    Invalidate 正常刷新 /** * 使整个视图无效.如果视图可见, * {@link #onDraw(android.graphics.Canvas)} 调用此方法后将在后续的UI刷新里调用o ...

  3. Android界面刷新之invalidate与postInvalidate的区别

    Android的invalidate与postInvalidate都是用来刷新界面的. 在UI主线程中,用invalidate():本质是调用View的onDraw()绘制. 主线程之外,用postI ...

  4. 解决:View调用invalidate()后不刷新onDraw()

    近来学android图片处理,按照例子来,自定义一个View,之后在Activity里面手动调用该View的invalidate()后,一直无法刷新onDraw() 上网搜了一下,有两种解决办法: 一 ...

  5. Android View 深度分析requestLayout、invalidate与postInvalidate

    前言 前几篇文章中,笔者对View的三大工作流程进行了详细分析,而这篇文章则详细讲述与三大工作流程密切相关的两个方法,分别是requestLayout和invalidate,如果对Viwe的三个工作流 ...

  6. 面试 -- requestLayout、invalidate与postInvalidate区别

    requestLayout: 从方法名字可以知道,“请求布局”,那就是说,如果调用了这个方法,那么对于一个子View来说,应该会重新进行布局流程.但是,真实情况略有不同,如果子View调用了这个方法, ...

  7. requestLayout, invalidate和postInvalidate的异同

    requestLayout 当一个VIEW的布局属性发生了变化的时候,可以调用该方法,让父VIEW调用onmeasure 和onlayout重新定位该view的位置,需要在UI线程调用 invalid ...

  8. 转--Invalidate和postInvalidate的更新view区别

    Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用. Android提供了Inva ...

  9. view, surfaceView, invalidate, postInvalidate, 刷新屏幕

    http://blog.csdn.net/linghu_java/article/details/9985489 1.view view在api中的结构 Java.lang.Object Androi ...

  10. invalidate()和postInvalidate() 的区别及使用

    Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中 ...

随机推荐

  1. Delphi 异常处理 详解

    [1] Exception类的定义在SysUtils单元中. [2] Delphi也支持不从Exception继承的异常类,但是我觉得这么做并不十分的明智. 一.异常的源 在Delphi的应用程序中, ...

  2. IntelliJ IDEA 2023.1永久激活方法

    IntelliJ IDEA永久激活方法 以下为破解教程: 注意:适用于IntelliJ IDEA 2021.3及其以上版本. 1. 清空IDEA以前的激活方法 大家可能在网上找了很多破解方法,比如修改 ...

  3. 扩展说明: 指令微调 Llama 2

    这篇博客是一篇来自 Meta AI,关于指令微调 Llama 2 的扩展说明.旨在聚焦构建指令数据集,有了它,我们则可以使用自己的指令来微调 Llama 2 基础模型. 目标是构建一个能够基于输入内容 ...

  4. 【CSS】如何复原被隐藏的滚动条?记一个看似简单的样式问题所引发的一系列思考

    壹 ❀ 引 故事的起因是这样的,某一个同事在封装了一个TableList组件,用于做表格视图渲染,但出于研发经验考虑上,他可能觉得表格若出滚动条可能会引发某些不可预估的小问题(毕竟一个基础组件会被用于 ...

  5. JS Leetcode 155. 最小栈 题解分析

    壹 ❀ 引 本题来自LeetCode155. 最小栈,难度简单,题目描述如下: 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈. push(x) -- 将元素 x ...

  6. JS leetcode 找到所有数组中消失的数字 题解分析

    壹 ❀ 引 十天前做的一道题了,一直没整理,今天才花时间去读了官方题解思路,这道题也凸显出了算法思路的重要性,执行耗时差的真不是一点半点.题目来自448. 找到所有数组中消失的数字,题目描述如下: 给 ...

  7. NC16810 [NOIP1999]拦截导弹

    题目链接 题目 题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某天,雷达 ...

  8. 【译】IEEE白皮书 6G 太赫兹技术的基本原理 2023版

    第一章 简介 太赫兹波是介于微波和光波之间的光谱区域,频率从 0.1THz ~ 10THz 之间,波长在 3mm ~ 30μm 之间.提供大块连续的频带范围以满足对 Tbit/s 内极高数据传输速率的 ...

  9. spring源码学习之设计模式

    目录 1.策略模式 2.观察者模式 3.装饰者模式 4.工厂模式 工厂方法模式 抽象工厂模式 工厂方法和抽象工厂的异同 5.单例模式 6.适配器模式与外观模式 适配器模式 外观模式 7.模板方法模式 ...

  10. 【Android逆向】frida 破解 滚动的天空

    1. apk 安装到手机中 2. 玩十次之后,会提示 充值 3. adb shell dumpsys window | grep mCurrentFocus 查看一些当前activity是哪一个 是 ...