View.invalidate() 与 requestLayout()

7 阅读1分钟
  • 只改内容/样式(不改大小位置) -> invalidate()

  • 改了大小/位置/层级 -> requestLayout()

  • postInvalidate ()在子线程的时候调用,(本质是通过 Handler 向主线程发送消息,最终调用 invalidate ())

invalidate():

一、先进行基础校验

1、检查View的状态,若 View 不可见(如 visibility 为 GONE/INVISIBLE)、正在执行动画,或未完成初始化(无 AttachInfo),直接跳过后续流程,不触发任何操作

     2、校验调用线程:必须在 UI 线程调用,若在子线程需使用 postInvalidate ()(本质是通过 Handler 向主线程发送消息,最终调用 invalidate ())

二、标记“脏区域”(确定重绘区域)

  • 核心逻辑:invalidate () 本质是调用内部的 invalidateInternal () 方法,标记当前 View 的 “脏区域”(即需要重绘的区域);

  • 区域区分:

    • 无参 invalidate ():标记整个 View 为脏区域,触发全量重绘;
    • 重载方法(invalidate (Rect dirty) /invalidate (int l, int t, int r, int b)):仅标记指定矩形区域为脏区域,实现局部重绘,优化性能;
  • 缓存处理:标记 View 的绘制缓存(drawing cache)失效,避免使用旧缓存导致显示异常,若无需清空缓存,可通过 invalidate (false) 跳过缓存失效步骤。

三、向上递归传递重绘请求(直至根节点)

标记脏区域后,不会立即绘制,而是将重绘请求沿 View 树向上传递:

  1. 从当前 View 开始,调用父容器(ViewGroup)的 invalidateChild () 方法,父容器会计算子 View 在自身坐标系下的脏区域,再继续向上传递;

  2. 最终传递至 ViewRootImpl(整个视图树的根节点),完成重绘请求的调度注册,此过程中各级父 View 会合并脏区域,减少重复绘制开销。

  3. 最终会调用到 // 最终触发调度遍历,scheduleTraversals(); 

    void scheduleTraversals() { // 防抖机制,可以避免在同一帧时间内多次调用invalidate(),也只会执行一次。 if (!mTraversalScheduled) { mTraversalScheduled = true; // 设置同步屏障,主线程将暂时停止处理普通的 Handler 消息(如点击事件、日志、业务逻辑), // 直到绘制完成后移除屏障。这确保了 UI 渲染消息一旦到来,能立刻获得 CPU 执行权。 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 注册 VSYNC 信号回调 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 通知渲染引擎 notifyRendererOfFramePending(); // 唤醒系统 // 此方法可以维持高能耗状态,确保这一帧能平滑地画出来,而不会因为系统降频导致掉帧 pokeDrawLockIfNeeded(); } }

流程总结

将整个 View.invalidate()ViewRootImpl.scheduleTraversals() 的过程串联起来,其本质是一次请求的向上溯源再向下派发绘制信号的调度机制。

向上溯源:不停地去找父View直到找到ViewRootImpl。

向下派发:通过VSYNC信号执行 View树的测量、布局和绘制操作。

步骤

组件

行为

1. 发起

View

调用 invalidate()

2. 冒泡

ViewGroup

标记 PFLAG_DIRTY,向上寻找 ViewRootImpl

3. 调度

ViewRootImpl

执行 scheduleTraversals()

4. 屏蔽

MessageQueue

插入 同步屏障,拦截普通消息。

5. 等待

Choreographer

等待硬件 VSYNC 信号

6. 回调

mTraversalRunnable

信号到达,执行 performTraversals()

以下区域可以不用看

引用

在 Android 中,view.invalidate() 是触发界面重绘的核心方法。它的调用链是一个从 子 View 向上溯源至 ViewRootImpl,再向下派发绘制信号 的过程

  • 当调用 invalidate() 时,最终会进入 invalidateInternal 方法,并进行“脏区域”的标记
  • ViewGroup 作为父容器,负责计算子 View 在父容器坐标系下的失效区域,并继续往上传递(这里分别有两条绘制路径:硬件绘制路径和软件绘制路径