在 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()

引用

相关推荐
不知疲倦的仄仄6 分钟前
第二天:深入理解 Selector:单线程高效管理多个 Channel
java·nio
期待のcode10 分钟前
Java虚拟机栈
java·开发语言·jvm
珂朵莉MM10 分钟前
全球校园人工智能算法精英大赛-产业命题赛-算法巅峰赛 2025年度画像
java·人工智能·算法·机器人
芒克芒克11 分钟前
本地部署SpringBoot项目
java·spring boot·spring
cute_ming12 分钟前
关于基于nodeMap重构DOM的最佳实践
java·javascript·重构
stevenzqzq16 分钟前
ctrl +B和ctrl+shift +B的区别
android·ide·android studio
sww_102619 分钟前
Netty原理分析
java·网络
似霰32 分钟前
HIDL Hal 开发笔记5----Same-Process HALs 实例分析
android·framework·hal
小突突突37 分钟前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
robotx44 分钟前
安卓16 设置壁纸中应用网格,有两个5X5的选项
android