在 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()。 |
引用