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 的区别:
| 工具 | 属性 | 触发条件 | 作用 |
|---|---|---|---|
ViewSetterTracer | debug.view.setter.trace | 每次调用 setter,无论值是否变化 | 追踪所有调用频次 |
ViewPropertyTracer | debug.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_INVALIDATEDinvalidate()会同时设置PFLAG_INVALIDATED和清除PFLAG_DRAWING_CACHE_VALIDupdateDisplayListIfDirty()检测到有 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 格式为:
ViewSetterTracer:VS|ClassName@hash|property(值 × 1000)ViewPropertyTracer:VP|ClassName@hash|property(值 × 1000)
相关源码位置
| 文件 | 关键方法 |
|---|---|
core/java/android/view/View.java:22203 | invalidateViewProperty() |
core/java/android/view/View.java:22225 | damageInParent() |
core/java/android/view/View.java:26220 | updateDisplayListIfDirty() |
core/java/android/view/ViewGroup.java:6257 | onDescendantInvalidated() |
core/java/android/view/ViewRootImpl.java:3639 | onDescendantInvalidated() |
core/java/android/view/ViewRootImpl.java:4213 | scheduleTraversals() |
core/java/android/view/ThreadedRenderer.java:795 | updateRootDisplayList() |
core/java/android/view/ThreadedRenderer.java:892 | draw() |
graphics/java/android/graphics/RenderNode.java:1081 | setAlpha() 等属性 setter |
core/java/android/view/ViewSetterTracer.java | Xiaomi 新增调试工具 |
core/java/android/view/ViewPropertyTracer.java | Xiaomi 新增调试工具 |