Android面试题之View的invalidate方法和postInvalidate方法有什么区别

简介: 本文探讨了Android自定义View中`invalidate()`和`postInvalidate()`的区别。`invalidate()`在UI线程中刷新View,而`postInvalidate()`用于非UI线程,通过消息机制切换到UI线程执行`invalidate()`。源码分析显示,`postInvalidate()`最终调用`ViewRootImpl`的`dispatchInvalidateDelayed`,通过Handler发送消息到UI线程执行刷新。

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”关注,和我一起每天进步一点点

我们在自定义View时免不了要使用invalidate方法,这个方法的作用大家也比较清楚,就是让我们的View进行刷新重新绘制的。但是postInvalidate方法可能就不是那么熟悉了,因为平时开发时invalidate方法相对而言会用得比较多。不过需要大家注意的是,面试官在问到View相关的问题时,就很有可能会问到postInvalidate方法,所以我们还是有必要来学习一下。

那invalidate方法和postInvalidate方法到底有什么区别呢?

invalidate方法和postInvalidate方法的区别

其实答案也很简单,就一句话:

invalidate方法和postInvalidate方法都是用于进行View的刷新,invalidate方法应用在UI线程中,而postInvalidate方法应用在非UI线程中,用于将线程切换到UI线程,postInvalidate方法最后调用的也是invalidate方法。

当然,空口无凭,我们还是来看看源码

invalidate方法和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.class

final ViewRootHandler mHandler = new ViewRootHandler();

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
   
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

ViewRootImpl中的dispatchInvalidateDelayed方法就是像ViewRootHandler发送了一个MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一个内部Handler类

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

我们可以看到ViewRootHandler中对于MSG_INVALIDATE消息的处理就是调用的View的invalidate方法。

总结

综上源码我们可以得出结论:

(1)postInvalidate方法调用了ViewRootImpl中的dispatchInvalidateDelayed方法向ViewRootImpl中的ViewRootHandler发送一个消息,最后调用的还是View的invalidate方法。

(2)因为ViewRootImpl是在UI线程的,所以postInvalidate方法的作用就是将非UI线程的刷新操作切换到UI线程,以便在UI线程中调用invalidate方法刷新View。所以如果我们自定义的View本身就是在UI线程,没有用到多线程的话,直接用invalidate方法来刷新View就可以了。而我们平时自定义View基本上都没有开起其他线程,所以这就是我们很少接触postInvalidate方法的原因

(3)所以一句话总结postInvalidate方法的作用就是:实现了消息机制,可以使我们在非UI线程也能调用刷新View的方法。


invalidate方法刷新View的调用过程分析

//View.class
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
   

    ...

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

    //invalidateCache为true表示全部重绘
    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 ((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;
            }

            //重点就在这里!!!
            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);
            }

            ...
        }
    }
}

View的invalidate方法最后调用的是invalidateInternal方法,invalidateInternal方法中的重点逻辑在上面已经标记出来了。

  • 其中的damage变量表示的是需要进行重绘的区域,后面在一系列的调用过程中会不断根据父布局来调整这个绘制区域。
  • invalidateInternal方法中通过调用View的父布局invalidateChild方法来请求重绘。那View的父布局是谁呢?这里有2种情况:要么是ViewGroup,要么就是ViewRootImpl类了。

我们先来看看ViewGroup中的invalidateChild方法

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

                if (drawAnimation) {
   
                    if (view != null) {
   
                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) {
   
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }

                ...

                //这里调用的是父布局的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;
    }
}

在ViewGroup的invalidateChild方法中有一个循环,循环里面会一直调用父布局的invalidateChildInParent方法,而View和ViewGroup的最终父布局都是ViewRootImpl

所以View中的invalidateInternal方法和ViewGroup中的invalidateChild方法最后殊途同归,都会调用到ViewRootImpl中的方法

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

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

    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方法进行绘制。按照我们对于View的绘制过程的理解,View的绘制流程是从ViewRootImpl的performTraversals方法开始的,下面我们来看看ViewRootImpl中的scheduleTraversals方法。

//ViewRootImpl.class
void scheduleTraversals() {
   
    if (!mTraversalScheduled) {
   
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //关键在这里!!!
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
   
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

居然没有我们要找的performTraversals方法。但是我们看到scheduleTraversals方法中调用了mChoreographer.postCallback方法,这里应该是关键

Choreoprapher类的作用是编排输入事件、动画事件和绘制事件的执行,它的postCallback方法的作用就是将要执行的事件放入内部的一个队列中,最后会执行传入的Runnable,这里也就是mTraversalRunnable。所以我们来看看mTraversalRunnable

//ViewRootImpl.class
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方法来,这里就是开始绘制View的地方啦!
            performTraversals();

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

当我们调用View的invalidate方法后,View会去不断向上调用父布局的绘制方法并在这个过程中计算需要重绘的区域,最终调用过程会走到ViewRootImpl中,调用的是ViewRootImpl的performTraversals执行重绘操作。

总结

  • 我们在自定义View时,当需要刷新View时,如果是在UI线程中,那就直接调用invalidate方法,如果是在非UI线程中,那就通过postInvalidate方法来刷新View
  • postInvalidate方法实现了消息机制,最终调用的也是invalidate方法来刷新View
  • invalidate方法最终调用的是ViewRootImpl中的performTraversals来重新绘制View

欢迎关注我的公众号AntDream查看更多精彩文章!

目录
相关文章
|
3月前
Android.mk(makefile)中几个符号的区别:=、 :=、 ?=、 +=
本文解释了在Android.mk文件中使用的几种赋值符号的区别,包括`=`(基本赋值)、`:=`(覆盖赋值)、`?=`(条件赋值,仅在变量未赋值时操作)、`+=`(追加赋值),并通过实验演示了这些符号的具体行为和效果。
184 1
|
2月前
|
Android开发 Kotlin
Android经典面试题之Kotlin的==和===有什么区别?
本文介绍了 Kotlin 中 `==` 和 `===` 操作符的区别:`==` 用于比较值是否相等,而 `===` 用于检查对象身份。对于基本类型,两者行为相似;对于对象引用,`==` 比较值相等性,`===` 检查引用是否指向同一实例。此外,还列举了其他常用比较操作符及其应用场景。
185 93
|
8天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
41 4
|
12天前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
20天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
24天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
21 2
|
1月前
|
编译器
经典面试题:变量的声明和定义有什么区别
在编程领域,变量的“声明”与“定义”是经典面试题之一。声明告诉编译器一个变量的存在,但不分配内存,通常包含变量类型和名称;而定义则为变量分配内存空间,一个变量必须至少被定义一次。简而言之,声明是告知变量形式,定义则是实际创建变量并准备使用。
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
108 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
1月前
|
前端开发 小程序 JavaScript
面试官:px、em、rem、vw、rpx 之间有什么区别?
面试官:px、em、rem、vw、rpx 之间有什么区别?
36 0
|
2月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
44 2