Android View 属性 setter 不走 invalidate() 的完整渲染路径分析

源码基于:frameworks/base(Xiaomi AOSP 分支)
分析属性:setAlpha / setRotation / setScaleX / setScaleY / setTranslationX / setTranslationY / setTranslationZ


背景:ViewSetterTracer 是什么

调试中经常能看到如下 Logcat:

06-11 20:27:33.638 D ViewSetterTrace: VS|KeyguardBottomAreaView@41a1545|alpha|1
06-11 20:27:33.638 D ViewSetterTrace: VS|KeyguardBottomAreaView@41a1545|rotation|0
06-11 20:27:33.638 D ViewSetterTrace: VS|KeyguardBottomAreaView@41a1545|scaleX|1
06-11 20:27:33.638 D ViewSetterTrace: VS|KeyguardBottomAreaView@41a1545|transX|0

这来自 Xiaomi 新增的 ViewSetterTracer,由系统属性 debug.view.setter.trace 控制:

// ViewSetterTracer.java
public static void traceFloat(View view, String property, float value) {
    String prefix = "VS|" + view.getClass().getSimpleName()
                  + "@" + Integer.toHexString(System.identityHashCode(view)) + "|";
    Trace.setCounter(prefix + property, (long)(value * 1000));  // 写入 Perfetto counter
    Log.d(TAG, prefix + property + "|" + value);                // 写入 Logcat
}

开启方法:

adb shell setprop debug.view.setter.trace 1              # 所有进程
adb shell setprop debug.view.setter.trace com.android.systemui  # 指定进程

ViewPropertyTracer 的区别:

工具属性触发条件作用
ViewSetterTracerdebug.view.setter.trace每次调用 setter,无论值是否变化追踪所有调用频次
ViewPropertyTracerdebug.view.prop.trace仅值发生变化追踪真实状态变化

核心结论

这些属性 setter 不走 onDraw(),但也不是什么都不做
它们走的是 RenderNode 属性路径,直接由 RenderThread 处理,完全绕过 View 绘制流水线。


完整调用链

路径一:硬件加速(正常场景)

flowchart TD
    A["调用 setRotation() / setScaleX() 等"] --> B["ViewSetterTracer.traceFloat()  写 Perfetto + Logcat"]
    B --> C{"值是否变化?"}
    C -->|否| Z["直接返回,无任何操作"]
    C -->|是| D["ViewPropertyTracer.traceFloat()  写 Perfetto"]
    D --> E["①invalidateViewProperty(true, false)"]
    E --> F{"isHardwareAccelerated() &&\nhasDisplayList()?"}
    F -->|是| G["damageInParent()\n→ mParent.onDescendantInvalidated()"]
    F -->|否| H["invalidate(false)\n软件渲染路径"]
    G --> I["ViewGroup.onDescendantInvalidated()\n逐层向上传递"]
    I --> J["ViewRootImpl.onDescendantInvalidated()\n→ scheduleTraversals()"]
    J --> K["mChoreographer.postVsyncCallback()\n等待下一个 VSYNC 信号"]
    D --> L["②mRenderNode.setRotationZ(rotation)\n→ JNI nSetRotationZ()\n直接写入 Native RenderNode"]
    L --> M["③invalidateViewProperty(false, true)\n再次 damageInParent()"]
    K --> N["VSYNC 到来\nperformTraversals()"]
    N --> O["ThreadedRenderer.draw()\n→ updateRootDisplayList()"]
    O --> P["view.updateDisplayListIfDirty()"]
    P --> Q{"PFLAG_INVALIDATED 是否设置?\n即是否调用过 invalidate()?"}
    Q -->|否 RenderNode属性变化| R["DisplayList 有效,跳过重新录制\ndispatchGetDisplayList()\nonDraw() 不被调用 ✓"]
    Q -->|是 invalidate调用| S["beginRecording() → draw() → onDraw()\n重新录制 DisplayList"]
    R --> T["syncAndDrawFrame()\n将更新后的 RenderNode 属性同步给 RenderThread"]
    S --> T
    T --> U["RenderThread 用新的变换属性\n重新合成画面 GPU 侧操作"]

    style L fill:#d4edda,stroke:#28a745
    style R fill:#d4edda,stroke:#28a745
    style U fill:#d4edda,stroke:#28a745
    style H fill:#fff3cd,stroke:#ffc107
    style S fill:#f8d7da,stroke:#dc3545

路径二:setAlpha() 的特殊情况

setAlpha() 有一个额外的分支——当子类重写 onSetAlpha() 并返回 true 时,会走 invalidate() 路径:

flowchart TD
    A["setAlpha(alpha)"] --> B["setAlphaInternal(alpha)  存储 mAlpha"]
    B --> C{"onSetAlpha() 返回 true?\n子类自己处理 alpha 绘制"}
    C -->|是 例如自定义软件层 View| D["mPrivateFlags |= PFLAG_ALPHA_SET\ninvalidateParentCaches()\ninvalidate(true)\n重新调用 onDraw()"]
    C -->|否 绝大多数普通 View| E["mPrivateFlags &= ~PFLAG_ALPHA_SET\ninvalidateViewProperty(true, false)\nmRenderNode.setAlpha(getFinalAlpha())\n走 RenderNode 路径"]

    style D fill:#f8d7da,stroke:#dc3545
    style E fill:#d4edda,stroke:#28a745

关键方法源码解析

1. View.invalidateViewProperty() — 核心分叉点

// View.java:22203
void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
    if (!isHardwareAccelerated()
            || !mRenderNode.hasDisplayList()
            || (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        // 软件渲染路径
        if (invalidateParent) {
            invalidateParentCaches();
        }
        if (forceRedraw) {
            mPrivateFlags |= PFLAG_DRAWN;
        }
        invalidate(false);  // ← 触发 onDraw()
    } else {
        // 硬件加速路径:只标记 dirty region,不触发 onDraw()
        damageInParent();
    }
    mPrivateFlags4 |= PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION;
}

关键判断条件:

  • isHardwareAccelerated() — View 是否在硬件加速窗口中(现代 Android 几乎全是)
  • mRenderNode.hasDisplayList() — RenderNode 是否已经有有效的 DisplayList

2. View.damageInParent() / ViewGroup.onDescendantInvalidated()

// View.java:22225
protected void damageInParent() {
    if (mParent != null && mAttachInfo != null) {
        mParent.onDescendantInvalidated(this, this);
    }
}
 
// ViewGroup.java:6257
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    // HW-only 路径:RenderThread 的 native 代码自己计算 damage 区域
    // 不需要矩形,直接标记脏标志
    mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
    mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;  // ← 清除缓存有效位
 
    if (mLayerType == LAYER_TYPE_SOFTWARE) {
        // 软件层必须 escalate 到完整 invalidate
        mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
        target = this;
    }
 
    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);  // 继续向上传递
    }
}

3. ViewRootImpl.onDescendantInvalidated()scheduleTraversals()

// ViewRootImpl.java:3639
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
    if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
        mIsAnimating = true;
    }
    invalidate();  // 设置整个窗口的 dirty 区域
}
 
void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();  // 向 Choreographer 注册 VSYNC 回调
    }
}
 
// ViewRootImpl.java:4213
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        postTraversalBarrier();
        // Xiaomi 小米优化:首帧跳过 VSYNC 等待
        mChoreographer.postVsyncCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalCallback);
    }
}

4. View.updateDisplayListIfDirty() — 决定是否重录 DisplayList

这是整个路径的核心判断

// View.java:26220
public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
 
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0   // 缓存被清除
            || !renderNode.hasDisplayList()
            || (mRecreateDisplayList)) {
 
        // 子树没有被 invalidate,但缓存被标记脏(damageInParent 会清除缓存有效位)
        if (renderNode.hasDisplayList() && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();  // 只通知子 View 更新,不重新录制
            return renderNode;  // ← 直接返回!onDraw() 不被调用 ✓
        }
 
        // PFLAG_INVALIDATED 被设置(调用了 invalidate())或 DisplayList 无效:
        // 重新录制 DisplayList
        final RecordingCanvas canvas = renderNode.beginRecording(width, height);
        try {
            draw(canvas);  // ← 这里才调用 onDraw()
        } finally {
            renderNode.endRecording();
        }
    }
    return renderNode;
}

核心逻辑:

  • damageInParent() 只清除了 PFLAG_DRAWING_CACHE_VALID未设置 PFLAG_INVALIDATED
  • invalidate() 会同时设置 PFLAG_INVALIDATED 和清除 PFLAG_DRAWING_CACHE_VALID
  • updateDisplayListIfDirty() 检测到有 DisplayList 且未设置 PFLAG_INVALIDATED 时,跳过 onDraw()

5. RenderNode 属性写入 — 全程 JNI

// RenderNode.java:1081
public boolean setAlpha(float alpha) {
    return nSetAlpha(mNativeRenderNode, alpha);  // 直接写 native
}
 
public boolean setRotationZ(float rotation) {
    return nSetRotationZ(mNativeRenderNode, rotation);
}
 
public boolean setTranslationX(float translationX) {
    return nSetTranslationX(mNativeRenderNode, translationX);
}

这些 setter 通过 JNI 直接修改 Native 侧的 RenderNode 结构体。在 syncAndDrawFrame() 时,RenderThread 会从 native RenderNode 读取最新的变换矩阵,无需 Java 层的任何 DisplayList 重录


软件渲染 vs 硬件加速对比

操作触发 onDraw()重录 DisplayList清除 DisplayList 缓存GPU 重合成
setRotation() (HW)是(damageInParent 清除)
setAlpha() (HW, 普通 View)
setAlpha() (onSetAlpha=true)
invalidate()
setRotation() (SW 渲染)N/A
requestLayout()是(若尺寸变化)是(若需要)

变换矩阵的计算顺序

RenderNode 的变换矩阵在渲染时按固定顺序计算,与 setter 调用顺序无关

Matrix transform = new Matrix();
transform.setTranslate(translationX, translationY);
transform.preRotate(rotationZ, pivotX, pivotY);
transform.preScale(scaleX, scaleY, pivotX, pivotY);

这意味着:

// 这两段代码等价
renderNode.setTranslationX(100);
renderNode.setScaleX(2);
 
renderNode.setScaleX(2);
renderNode.setTranslationX(100);

日志含义:全是 0/1 的 reset 操作

VS|KeyguardBottomAreaView@41a1545|alpha|1
VS|KeyguardBottomAreaView@41a1545|rotation|0
VS|KeyguardBottomAreaView@41a1545|scaleX|1
VS|KeyguardBottomAreaView@41a1545|scaleY|1
VS|KeyguardBottomAreaView@41a1545|transX|0
VS|KeyguardBottomAreaView@41a1545|transY|0
VS|KeyguardBottomAreaView@41a1545|transZ|0

这是 KeyguardBottomAreaView重置/初始化变换状态(所有属性回到默认值),通常出现在:

  • Keyguard 显示/隐藏过渡动画结束时
  • ViewPropertyAnimator / ObjectAnimator 结束后的状态回收
  • INVISIBLE 变回 VISIBLE 时的初始化

因为 ViewSetterTracer 记录每一次 setter 调用(包括值不变的调用),所以能看到完整的重置序列。


性能意义

为什么属性动画比 invalidate() 快?

环节属性动画invalidate() 动画
每帧 UI 线程工作设置 RenderNode 属性(JNI,微秒级)onDraw() 重新绘制(毫秒级)
DisplayList不重录每帧重录
GPU 工作仅重新合成(变换矩阵)重新上传绘制命令
线程模型UI 线程设置属性,RenderThread 独立执行必须等 UI 线程完成 onDraw()

LAYER_TYPE_SOFTWARE 的性能陷阱

当父 View 设置了 LAYER_TYPE_SOFTWARE 时,onDescendantInvalidated() 会强制 escalate:

// ViewGroup.java:6278
if (mLayerType == LAYER_TYPE_SOFTWARE) {
    mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;  // ← 强制完整重绘!
    target = this;
}

这意味着软件层父容器内的属性动画会触发 onDraw(),是常见的性能 anti-pattern。


调试技巧

开启完整 View 属性追踪

# ViewSetterTracer:追踪每次 setter 调用(含无变化调用)
adb shell setprop debug.view.setter.trace com.android.systemui
 
# ViewPropertyTracer:追踪值变化(Perfetto counter)
adb shell setprop debug.view.prop.trace com.android.systemui
 
# 重启 SystemUI 生效
adb shell killall com.android.systemui

在 Perfetto 中查看

counter track 格式为:

  • ViewSetterTracerVS|ClassName@hash|property(值 × 1000)
  • ViewPropertyTracerVP|ClassName@hash|property(值 × 1000)

相关源码位置

文件关键方法
core/java/android/view/View.java:22203invalidateViewProperty()
core/java/android/view/View.java:22225damageInParent()
core/java/android/view/View.java:26220updateDisplayListIfDirty()
core/java/android/view/ViewGroup.java:6257onDescendantInvalidated()
core/java/android/view/ViewRootImpl.java:3639onDescendantInvalidated()
core/java/android/view/ViewRootImpl.java:4213scheduleTraversals()
core/java/android/view/ThreadedRenderer.java:795updateRootDisplayList()
core/java/android/view/ThreadedRenderer.java:892draw()
graphics/java/android/graphics/RenderNode.java:1081setAlpha() 等属性 setter
core/java/android/view/ViewSetterTracer.javaXiaomi 新增调试工具
core/java/android/view/ViewPropertyTracer.javaXiaomi 新增调试工具