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

引用

相关推荐
ManThink Technology23 分钟前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络
invicinble27 分钟前
springboot的核心实现机制原理
java·spring boot·后端
人道领域35 分钟前
SSM框架从入门到入土(AOP面向切面编程)
java·开发语言
大模型玩家七七1 小时前
梯度累积真的省显存吗?它换走的是什么成本
java·javascript·数据库·人工智能·深度学习
CodeToGym1 小时前
【Java 办公自动化】Apache POI 入门:手把手教你实现 Excel 导入与导出
java·apache·excel
凡人叶枫2 小时前
C++中智能指针详解(Linux实战版)| 彻底解决内存泄漏,新手也能吃透
java·linux·c语言·开发语言·c++·嵌入式开发
JMchen1232 小时前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio
阔皮大师2 小时前
INote轻量文本编辑器
java·javascript·python·c#
小法师爱分享2 小时前
StickyNotes,简单便签超实用
java·python
qq_297574672 小时前
Linux 服务器 Java 开发环境搭建保姆级教程
java·linux·服务器