在 Android 中执行 View.invalidate() 方法后经历了什么

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

0x00、View.invalidate()

当调用 invalidate() 时,最终会进入 invalidateInternal 方法,并进行"脏区域"的标记。

java 复制代码
    /**
     * 这里注释可以看出,此方法会触发onDraw方法,且只能在UI线程执行,非UI线程使用postInvalidate()
     * Invalidate the whole view. If the view is visible,
     * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
     * the future.
     * <p>
     * This must be called from a UI thread. To call from a non-UI thread, call
     * {@link #postInvalidate()}.
     */
    public void invalidate() {
        invalidate(true);
    }
    
		@UnsupportedAppUsage
    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) {
        //...
				// 1. 如果 View 不可见或者正在执行动画,直接跳过返回
        if (skipInvalidate()) {
            return;
        }

        // Reset content capture caches
        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
        mContentCaptureSessionCached = false;
				// 2.1 设置"脏区域",标记需要重绘制的区域
        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;
            }
						// 2.2 标记该 View 的内容失效,需要重新绘制
            mPrivateFlags |= PFLAG_DIRTY;

            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

						// 3. 将重绘请求向上传递给父容器
            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            // 获取到该View的父控件,即 ViewGroup
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                // 3.1 设置失效的矩形区域
                damage.set(l, t, r, b);
                // 3.2 调用父控件的 invalidateChild
                p.invalidateChild(this, damage);
            }

            //...
        }
    }

0x01、ViewGroup.invalidateChild()

ViewGroup 作为父容器,负责计算子 View 在父容器坐标系下的失效区域,并继续往上传递。

这里分别有两条绘制路径:硬件绘制路径和软件绘制路径

java 复制代码
public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        // 1.**硬件绘制路径,**目前的主流Android版本应该是默认开启硬件加速的
        if (attachInfo != null && attachInfo.mHardwareAccelerated) {
            // HW accelerated fast path
            // 硬件加速通道,这里会执行后返回,不再使用软件绘制,这个也是View性能优化的关键
            onDescendantInvalidated(child, child);
            return;
        }
        
				// 2.**软件绘制路径,**使用软件计算哪块区域是需要重新绘制的
        ViewParent parent = this;
        if (attachInfo != null) {
            // If the child is drawing an animation, we want to copy this flag onto
            // ourselves and the parent to make sure the invalidate request goes
            // through
            final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0;

            // Check whether the child that requests the invalidate is fully opaque
            // Views being animated or transformed are not considered opaque because we may
            // be invalidating their old position and need the parent to paint behind them.
            Matrix childMatrix = child.getMatrix();
            // Mark the child as dirty, using the appropriate flag
            // Make sure we do not set both flags at the same time

            if (child.mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            final int[] location = attachInfo.mInvalidateChildLocation;
            // 获取子 View 的位置
            location[CHILD_LEFT_INDEX] = child.mLeft;
            location[CHILD_TOP_INDEX] = child.mTop;
            // 如果 View 有旋转、缩放等变换(Matrix)
            if (!childMatrix.isIdentity() ||
                    (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                RectF boundingRect = attachInfo.mTmpTransformRect;
                boundingRect.set(dirty);
                Matrix transformMatrix;
                if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                    Transformation t = attachInfo.mTmpTransformation;
                    boolean transformed = getChildStaticTransformation(child, t);
                    if (transformed) {
                        transformMatrix = attachInfo.mTmpMatrix;
                        transformMatrix.set(t.getMatrix());
                        if (!childMatrix.isIdentity()) {
                            transformMatrix.preConcat(childMatrix);
                        }
                    } else {
                        transformMatrix = childMatrix;
                    }
                } else {
                    transformMatrix = childMatrix;
                }
                // 将 dirty 矩形按矩阵进行变换映射
                transformMatrix.mapRect(boundingRect);
                // 重新计算变换后的矩形边界
                dirty.set((int) Math.floor(boundingRect.left),
                        (int) Math.floor(boundingRect.top),
                        (int) Math.ceil(boundingRect.right),
                        (int) Math.ceil(boundingRect.bottom));
            }
						// 核心循环:向上冒泡
            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;
                    }
                }

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                // 标记当前层级为脏区域 DIRTY
                if (view != null) {
                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
                    }
                }

								// 向上递归,并获取下一个父节点
                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    // 处理父节点的 Matrix 变换
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        // 更新 dirty 矩形区域
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }

0x02、ViewGroup.onDescendantInvalidated() 硬件绘制路径

可以看到硬件加速 开启后的重绘路径。相比于之前分析的 invalidateChild(软件绘制路径),它的逻辑极其精简。

java 复制代码
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
        /*
         * HW-only, Rect-ignoring damage codepath
         *
         * We don't deal with rectangles here, since RenderThread native code computes damage for
         * everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
         */

        // if set, combine the animation flag into the parent
        mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
				// 脏标记处理
        if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
            // We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
            // optimization in provides in a DisplayList world.
            mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;

            // simplified invalidateChildInParent behavior: clear cache validity to be safe...
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // ... and mark inval if in software layer that needs to repaint (hw handled in native)
        // 如果某个父 View 设置了 setLayerType(LAYER_TYPE_SOFTWARE)
        // 这个父 View 就必须像普通 View 一样走完整的 invalidate 流程
        // 因为它需要 CPU 重新把子 View 绘制到它的 Bitmap 缓存里
        if (mLayerType == LAYER_TYPE_SOFTWARE) {
            // Layered parents should be invalidated. Escalate to a full invalidate (and note that
            // we do this after consuming any relevant flags from the originating descendant)
            mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
            target = this;
        }
				// 递归向上传递,一直传递到 ViewRootImpl 的 onDescendantInvalidated
        if (mParent != null) {
            mParent.onDescendantInvalidated(this, target);
        }
    }

硬件加速下,脏区域的计算交给了底层的 RenderThread 和 GPU,而不是在 Java 层手动算矩形。

在软件绘制时,CPU 需要知道精确的 Rect 区域,以便只对该区域进行像素填充。但在硬件加速(HWUI)中:

  • 每个 View 都有一个对应的 RenderNode(在 Native 层)。
  • View 的绘制指令被记录在 DisplayList 中。
  • RenderThread(渲染线程) 知道所有 View 的层级和位置。当某个 View 失效时,渲染线程会自动合并受影响的区域。因此,Java 层不需要费力去算坐标偏移和矩形交集,只需要告知系统:这个View失效了,需要更新

0x03、ViewRootImpl 冒泡的终点

3.1 软件绘制路径计算的终点 : ViewRootImpl.invalidateChildInParent()

java 复制代码
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
				// // dirty 为 null 代表全量刷新
        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);
            }
        }
				// 触发刷新,此方法内部会调用 scheduleTraversals()。
        invalidateRectOnScreen(dirty);
				// 返回 null,告知 ViewGroup 冒泡循环结束
        return null;
    }
    
    private void invalidateRectOnScreen(Rect dirty) {
        if (DEBUG_DRAW) Log.v(mTag, "invalidateRectOnScreen: " + dirty);
        final Rect localDirty = mDirty;

        // Add the new dirty rect to the current one
        // 合并脏区域
        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
        // Intersect with the bounds of the window to skip
        // updates that lie outside of the visible region
        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();
        }
    }

3.2 硬件绘制路径终点: ViewRootImpl.onDescendantInvalidated()

与软件路径那种精细计算矩形、处理滚动偏移的逻辑相比,硬件加速路径的终点显得极其"简单粗暴"的。这种简化正是硬件加速性能优势的体现。

java 复制代码
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
        if (sToolkitEnableInvalidateCheckThreadFlagValue) {
            checkThread();
        }
        if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
            mIsAnimating = true;
        }
        invalidate();
    }

    @UnsupportedAppUsage
    void invalidate() {
		    // 全量标记脏区域
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
		        // 最终触发调度遍历,
            scheduleTraversals();
        }
    }

可以看到不管是软件绘制还是硬件绘制最终都会触发 scheduleTraversals()

java 复制代码
		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();
        }
    }

Choreographer 会等待下一个硬件 VSYNC 信号。信号到达后,它会发送一个异步消息 来执行 mTraversalRunnable

mTraversalRunnable :这个 Runnable 内部执行的就是著名的 doTraversal() -> performTraversals()(包含 Measure、Layout、Draw 流程)

java 复制代码
		final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
		final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

		void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // 注意在VSYNC信号到来后马上就移除了同步屏障
            // 如果没有移除,会导致正常的普通消息(如事件、日志、业务逻辑)可能无法正常执行从而ANR
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
						// 执行测量、布局、绘制动作
            performTraversals();

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

通知渲染线程

java 复制代码
    /**
     * Notifies the HardwareRenderer that a new frame will be coming soon.
     * Currently only {@link ThreadedRenderer} cares about this, and uses
     * this knowledge to adjust the scheduling of off-thread animations
     */
    void notifyRendererOfFramePending() {
		    // 通知渲染线程新的一帧数据马上就要到了,这个方法执行之后会调用native方法
        if (mAttachInfo.mThreadedRenderer != null) {
            mAttachInfo.mThreadedRenderer.notifyFramePending();
        }
    }

渲染线程执行native方法 HardwareRenderer.notifyFramePending()

java 复制代码
    /**
     * Notifies the hardware renderer that a call to {@link FrameRenderRequest#syncAndDraw()} will
     * be coming soon. This is used to help schedule when RenderThread-driven animations will
     * happen as the renderer wants to avoid producing more than one frame per vsync signal.
     */
    public void notifyFramePending() {
        nNotifyFramePending(mNativeProxy);
    }

渲染线程将UI线程的绘制操作转成GPU执行指令,GPU渲染后(填充图形缓冲)就会交给SurfaceFlinger进行图形合成,最后交给显示驱动进行屏幕显示。

0x04、宏观流程总结

将整个 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()

引用

相关推荐
大阿明4 小时前
Spring Boot(快速上手)
java·spring boot·后端
bearpping5 小时前
Java进阶,时间与日期,包装类,正则表达式
java
邵奈一5 小时前
清明纪念·时光信笺——项目运行指南
java·实战·项目
sunwenjian8865 小时前
Java进阶——IO 流
java·开发语言·python
sinat_255487815 小时前
读者、作家 Java集合学习笔记
java·笔记·学习
皮皮林5515 小时前
如何画出一张优秀的架构图?(老鸟必备)
java
百锦再5 小时前
Java 并发编程进阶,从线程池、锁、AQS 到并发容器与性能调优全解析
java·开发语言·jvm·spring·kafka·tomcat·maven
RainyJiang6 小时前
谱写Kotlin协程面试进行曲-进阶篇(第二乐章)
面试·kotlin·android jetpack
森林猿6 小时前
java-modbus-读取-modbus4j
java·网络·python
tobias.b6 小时前
计算机基础知识-数据结构
java·数据结构·考研