Android UI 刷新机制解析

157 阅读12分钟

Android UI 刷新机制深度解析

注:本文是记录学习过程。

目录

  1. 传统 View 体系刷新机制
  2. Compose 体系刷新机制
  3. 两者对比与总结

一、传统 View 体系刷新机制

1.1 核心组件架构

Android View 体系的刷新机制主要涉及以下核心组件:

屏幕显示流程
    │
    ▼
┌──────────────┐
│  VSync 信号   │  ← 垂直同步信号(60Hz/90Hz/120Hz)
│  硬件产生     │
└───────┬──────┘
        │ 每 16.6ms 一次(60fps)
        ▼
┌──────────────────┐
│  Choreographer   │  ← 编舞者,协调渲染时机
│  (编舞者)         │
└───────┬──────────┘
        │
        ├─────► INPUT 输入事件
        ├─────► ANIMATION 动画
        ├─────► TRAVERSAL 遍历(绘制)
        └─────► COMMIT 提交
        │
        ▼
┌──────────────────┐
│  ViewRootImpl    │  ← 连接 WindowManager 和 DecorView
└───────┬──────────┘
        │
        ▼
┌──────────────────────────────────┐
│  三大绘制流程                     │
│  1. Measure  (测量)              │
│  2. Layout   (布局)              │
│  3. Draw     (绘制)              │
└───────┬──────────────────────────┘
        │
        ▼
┌──────────────────┐
│  Surface         │  ← 画布
│  (双缓冲机制)     │
└───────┬──────────┘
        │
        ▼
┌──────────────────┐
│  SurfaceFlinger  │  ← 系统服务,合成图层
└───────┬──────────┘
        │
        ▼
┌──────────────────┐
│  显示器           │  ← 显示到屏幕
└──────────────────┘

1.2 VSync 信号机制

VSync(Vertical Synchronization,垂直同步)是显示系统的时钟信号,用于同步屏幕刷新和帧缓冲区更新。

VSync 的作用:

  • 防止画面撕裂(Screen Tearing)
  • 同步 CPU/GPU 渲染和屏幕刷新
  • 提供统一的帧率基准(60fps = 16.6ms/帧)

源码实现:

// frameworks/base/core/java/android/view/Choreographer.java
public final class Choreographer {
    
    // VSync 信号到达时的回调
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver {
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            // 收到 VSync 信号
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            
            // 触发消息处理
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }
    }
    
    // 执行绘制任务队列
    void doFrame(long frameTimeNanos, int frame) {
        // 1. 处理输入事件
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
        
        // 2. 处理动画
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        
        // 3. 处理布局和绘制
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
        
        // 4. 提交
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    }
}

1.3 Choreographer(编舞者)

Choreographer 是 Android 4.1 引入的核心类,负责协调动画、输入和绘制的时机。

核心职责:

  1. 接收 VSync 信号
  2. 管理四种类型的回调任务
  3. 按优先级执行回调
  4. 保证渲染在 16.6ms 内完成

回调类型优先级:

优先级类型说明
1CALLBACK_INPUT输入事件处理
2CALLBACK_ANIMATION动画更新
3CALLBACK_TRAVERSAL布局和绘制
4CALLBACK_COMMIT提交操作

1.4 View 刷新流程

1.4.1 触发刷新

View.invalidate() 源码:

// frameworks/base/core/java/android/view/View.java
public class View {
    
    // 标记需要重绘
    public void invalidate() {
        invalidate(true);
    }
    
    void invalidate(boolean invalidateCache) {
        // 标记绘制标志位
        mPrivateFlags |= PFLAG_DIRTY;
        
        // 向上传递刷新请求
        final ViewParent p = mParent;
        if (p != null) {
            p.invalidateChild(this, null);  // 通知父容器
        }
    }
}
1.4.2 向上传递刷新请求
// frameworks/base/core/java/android/view/ViewGroup.java
public abstract class ViewGroup extends View {
    
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        // 计算脏区域
        final int[] location = new int[2];
        
        ViewParent parent = this;
        
        // 向上传递,最终到达 ViewRootImpl
        do {
            parent = parent.invalidateChildInParent(location, dirty);
        } while (parent != null);
    }
}
1.4.3 ViewRootImpl 调度绘制
// frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent {
    
    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        // 检查线程(必须在主线程)
        checkThread();
        
        // 请求下一帧绘制
        scheduleTraversals();
        return null;
    }
    
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            
            // 设置同步屏障,优先处理绘制消息
            mTraversalBarrier = mHandler.getLooper().getQueue()
                .postSyncBarrier();
            
            // 向 Choreographer 注册 TRAVERSAL 回调
            mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL,
                mTraversalRunnable,  // 执行绘制
                null
            );
        }
    }
    
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();  // 执行遍历
        }
    }
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            
            // 移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            
            // 执行三大流程
            performTraversals();
        }
    }
}

1.5 三大绘制流程

1.5.1 完整流程
// ViewRootImpl.java
private void performTraversals() {
    
    // ========== 1. Measure 测量 ==========
    // 确定 View 的大小
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
    // ========== 2. Layout 布局 ==========
    // 确定 View 的位置
    performLayout(lp, mWidth, mHeight);
    
    // ========== 3. Draw 绘制 ==========
    // 绘制到 Surface
    performDraw();
}
1.5.2 Measure 测量
// Measure: 测量 View 大小
private void performMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    mView.measure(widthMeasureSpec, heightMeasureSpec);
}

// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // 缓存机制:如果测量规格没变,可以跳过
    boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    
    if (forceLayout || needsLayout) {
        // 调用 onMeasure,子类重写实现具体测量逻辑
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        // 标记测量完成
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
}

// 自定义 View 需要重写
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 设置测量后的宽高
    setMeasuredDimension(
        getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
    );
}

MeasureSpec 说明:

MeasureSpec 是一个 32 位 int 值,高 2 位表示模式,低 30 位表示尺寸。

模式说明
EXACTLY精确模式,指定了确切的大小(match_parent 或具体数值)
AT_MOST最大模式,不超过父容器的大小(wrap_content)
UNSPECIFIED未指定模式,父容器不限制子 View 大小
1.5.3 Layout 布局
// Layout: 确定 View 位置
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
}

// View.java
public void layout(int l, int t, int r, int b) {
    // 保存旧位置
    int oldL = mLeft;
    int oldT = mTop;
    int oldR = mRight;
    int oldB = mBottom;
    
    // 设置新位置
    boolean changed = setFrame(l, t, r, b);
    
    // 如果位置或尺寸改变,回调 onLayout
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    }
}

// ViewGroup 需要重写,确定子 View 位置
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
1.5.4 Draw 绘制
// Draw: 绘制到 Surface
private void performDraw() {
    // 使用硬件加速或软件绘制
    if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
        // 硬件加速绘制
        mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    } else {
        // 软件绘制
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;
        }
    }
}

// View.java - 绘制流程
public void draw(Canvas canvas) {
    // 1. 绘制背景
    drawBackground(canvas);
    
    // 2. 保存图层(如果需要)
    saveLayer();
    
    // 3. 绘制自己的内容
    onDraw(canvas);
    
    // 4. 绘制子 View
    dispatchDraw(canvas);
    
    // 5. 绘制装饰(前景、滚动条等)
    onDrawForeground(canvas);
    
    // 6. 绘制默认焦点高亮
    drawDefaultFocusHighlight(canvas);
}

// 自定义 View 重写此方法实现绘制
protected void onDraw(Canvas canvas) {
    // 绘制具体内容
}

1.6 双缓冲机制

Android 使用双缓冲(Double Buffering)来避免画面闪烁和撕裂。

原理:

帧 N-1                     帧 N                      帧 N+1
                          
┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│  Front Buffer│  显示   │  Back Buffer │  绘制   │  Front Buffer│  显示
│  (显示屏读取) │ ───────►│  (CPU/GPU写) │ ───────►│  (交换后)     │
└──────────────┘         └──────────────┘         └──────────────┘
                         
                         VSync 到达时交换缓冲区

流程说明:

  1. CPU/GPU 绘制到 Back Buffer(后台缓冲区)
  2. VSync 信号到达
  3. 交换 Front Buffer 和 Back Buffer
  4. 显示器读取 Front Buffer 显示到屏幕

1.7 完整时间线

时间线 ────────────────────────────────────────────────────────────►

[T0] 用户触发 view.invalidate()
     │
     ▼
     View 标记 PFLAG_DIRTY
     │
     ▼
     ViewGroup.invalidateChild() 向上传递
     │
     ▼
     ViewRootImpl.invalidateChildInParent()
     │
     ▼
     ViewRootImpl.scheduleTraversals()
     │
     ▼
     设置同步屏障
     │
     ▼
     Choreographer.postCallback(TRAVERSAL, ...)
     │
     │ 【等待下一个 VSync 信号】
     │
[T1] ⏰ VSync 信号到达(~16.6ms 后,60fps)
     │
     ▼
     Choreographer.doFrame(frameTimeNanos)
     │
     ├──► CALLBACK_INPUT    (处理触摸、按键等输入事件)
     │
     ├──► CALLBACK_ANIMATION (更新动画状态)
     │
     ├──► CALLBACK_TRAVERSAL (执行 View 绘制)
     │    │
     │    ├──► performMeasure()  测量 View 大小
     │    │    └──► onMeasure()
     │    │
     │    ├──► performLayout()   确定 View 位置
     │    │    └──► onLayout()
     │    │
     │    └──► performDraw()     绘制 View
     │         ├──► drawBackground()   绘制背景
     │         ├──► onDraw()           绘制内容
     │         ├──► dispatchDraw()     绘制子 View
     │         └──► onDrawForeground() 绘制前景
     │
     └──► CALLBACK_COMMIT
     │
     ▼
     移除同步屏障
     │
     ▼
     Surface.unlockCanvasAndPost()  提交到 SurfaceFlinger
     │
[T2] SurfaceFlinger 合成所有 Surface 图层
     │
     ▼
     交换缓冲区(双缓冲)
     │
     ▼
[T3] 显示到屏幕(下一个 VSync)

二、Compose 体系刷新机制

2.1 核心架构

Compose 采用声明式 UI 范式,与传统 View 体系有本质区别。

Compose 响应式架构
    │
    ▼
┌──────────────────┐
│  State (状态)     │  ← 数据源(单一数据源)
│  remember/mutableStateOf
└───────┬──────────┘
        │ 状态变化
        ▼
┌──────────────────┐
│  Snapshot System │  ← 快照系统,追踪状态变化
└───────┬──────────┘
        │
        ▼
┌──────────────────┐
│  Recomposition   │  ← 智能重组(只重组变化部分)
│  (重新执行 @Composable)
└───────┬──────────┘
        │
        ▼
┌──────────────────┐
│  Composition     │  ← 生成 Slot TableUI 树)
└───────┬──────────┘
        │
        ▼
┌──────────────────┐
│  Layout          │  ← 测量和布局(类似 ViewMeasure/Layout)
└───────┬──────────┘
        │
        ▼
┌──────────────────┐
│  Drawing         │  ← 绘制(类似 ViewDraw)
└───────┬──────────┘
        │
        ▼
┌──────────────────┐
│  Choreographer   │  ← 最终还是通过 VSync 刷新
└───────┬──────────┘
        │
        ▼
┌──────────────────┐
│  屏幕显示         │
└──────────────────┘

2.2 State(状态管理)

Compose 的核心是状态驱动 UI,状态变化自动触发 UI 更新。

2.2.1 State 接口
// androidx/compose/runtime/State.kt
@Stable
interface State {
    val value: T
}

// androidx/compose/runtime/MutableState.kt
interface MutableState : State {
    override var value: T
    operator fun component1(): T
    operator fun component2(): (T) -> Unit
}
2.2.2 创建和使用 State
// 基本用法
@Composable
fun Counter() {
    // remember: 保存状态,在重组时不会重置
    // mutableStateOf: 创建可观察的状态
    var count by remember { mutableStateOf(0) }
    
    Column {
        Text("Count: $count")  // 读取状态,建立订阅关系
        
        Button(onClick = { count++ }) {  // 修改状态,触发重组
            Text("Increment")
        }
    }
}

// 状态提升(State Hoisting)
@Composable
fun StatefulCounter() {
    var count by remember { mutableStateOf(0) }
    StatelessCounter(
        count = count,
        onIncrement = { count++ }
    )
}

@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit) {
    Column {
        Text("Count: $count")
        Button(onClick = onIncrement) {
            Text("Increment")
        }
    }
}

2.3 Snapshot System(快照系统)

Snapshot 是 Compose 的核心机制,用于追踪状态变化并触发重组。

2.3.1 Snapshot 原理
// androidx/compose/runtime/snapshots/Snapshot.kt
abstract class Snapshot(
    val id: Int,
    val invalid: SnapshotIdSet
) {
    // 读观察器:记录哪些 Composable 读取了状态
    var readObserver: ((Any) -> Unit)? = null
    
    // 写观察器:记录状态的修改
    var writeObserver: ((Any) -> Unit)? = null
}

// 可变快照
class MutableSnapshot(
    id: Int,
    invalid: SnapshotIdSet,
    readObserver: ((Any) -> Unit)?,
    writeObserver: ((Any) -> Unit)?
) : Snapshot(id, invalid) {
    
    // 应用变化
    fun apply(): SnapshotApplyResult {
        // 将快照中的修改应用到全局状态
        // 检测冲突
        // 通知观察者
    }
}
2.3.2 状态读写拦截
// 读取状态时
fun  State.getValue(): T {
    // 通知当前快照:这个 Composable 依赖此状态
    Snapshot.current.readObserver?.invoke(this)
    return this.value
}

// 修改状态时
fun  MutableState.setValue(value: T) {
    // 通知快照系统:状态已修改
    Snapshot.current.writeObserver?.invoke(this)
    this.value = value
    
    // 触发重组
    scheduleRecomposition()
}
2.3.3 实际运行示例
@Composable
fun Example() {
    var count by remember { mutableStateOf(0) }
    
    // 编译器生成的代码(简化):
    // 1. 创建快照
    val snapshot = Snapshot.takeMutableSnapshot(
        readObserver = { state ->
            // 记录:Example Composable 依赖 count 状态
            currentComposer.recordRead(state)
        },
        writeObserver = { state ->
            // 记录:count 状态已修改
            state.recordModification()
            // 标记需要重组
            invalidateComposable(Example)
        }
    )
    
    // 2. 在快照中执行
    snapshot.enter {
        Text("Count: $count")  // 触发 readObserver
    }
}

2.4 Recomposition(重组)

重组是 Compose 响应状态变化的核心机制。

2.4.1 Recomposer 源码
// androidx/compose/runtime/Recomposer.kt
class Recomposer(
    effectCoroutineContext: CoroutineContext
) : CompositionContext() {
    
    // 需要重组的作用域列表
    private val snapshotInvalidations = mutableListOf()
    
    // 调度重组
    internal fun scheduleRecompose(scope: RecomposeScopeImpl) {
        synchronized(stateLock) {
            snapshotInvalidations.add(scope)
        }
        
        // 触发重组流程
        deriveStateLocked()
    }
    
    // 执行重组
    private suspend fun recompose() {
        // 在协程中执行
        withContext(effectCoroutineContext) {
            // 1. 应用快照变化
            Snapshot.sendApplyNotifications()
            
            // 2. 遍历需要重组的 Composable
            snapshotInvalidations.fastForEach { scope ->
                if (scope.isInvalidated) {
                    // 重新执行 Composable 函数
                    scope.compose(recomposer = this@Recomposer)
                }
            }
            
            // 3. 清空队列
            snapshotInvalidations.clear()
        }
    }
}
2.4.2 智能重组范围

Compose 编译器会自动识别重组边界,只重组受影响的部分。

@Composable
fun SmartRecomposition() {
    var counter by remember { mutableStateOf(0) }
    
    Column {
        // ========== 重组范围 1 ==========
        // 依赖 counter,会在 counter 变化时重组
        Text("Counter: $counter")
        
        Button(onClick = { counter++ }) {
            Text("Increment")
        }
        
        // ========== 重组范围 2 ==========
        // 不依赖 counter,不会重组
        StaticContent()
        
        // ========== 重组范围 3 ==========
        // 依赖 counter,但有条件判断
        if (counter > 5) {
            Text("Counter is greater than 5")
        }
    }
}

@Composable
fun StaticContent() {
    // 这个函数不读取任何状态,永远不会重组
    Text("This is static")
}
2.4.3 编译器生成的代码
// 编译前
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Text("Count: $count")
}

// 编译后(简化版)
@Composable
fun Counter($composer: Composer, $changed: Int) {
    // 开始可重启组
    $composer.startRestartGroup(key = 123)
    
    // 缓存状态
    val count = $composer.cache {
        mutableStateOf(0)
    }
    
    // 开始可替换组(重组边界)
    $composer.startReplaceableGroup(key = 456)
    
    // 调用 Text,传入 Composer
    Text(
        text = "Count: ${count.value}",  // 读取状态,建立依赖
        $composer = $composer,
        $changed = 0
    )
    
    $composer.endReplaceableGroup()
    
    // 结束可重启组,返回重组作用域
    val scope = $composer.endRestartGroup()
    
    // 如果状态变化,invalidate 会触发 scope.compose()
}

2.5 Composition(组合)

Composition 是 Compose UI 的树形结构表示。

2.5.1 Slot Table

Compose 使用 Slot Table 存储 UI 树,而不是像 View 那样创建对象树。

// androidx/compose/runtime/SlotTable.kt
internal class SlotTable {
    // 扁平化的数组存储
    var slots: Array = emptyArray()
    
    // 组索引
    var groups: IntArray = IntArray(0)
    
    // 插入数据
    fun insert(index: Int, value: Any?) {
        slots[index] = value
    }
    
    // 读取数据
    fun get(index: Int): Any? {
        return slots[index]
    }
}

优势:

  • 内存占用小(数组 vs 对象树)
  • 结构共享,提高性能
  • 支持增量更新

2.6 Layout 和 Drawing

2.6.1 Layout 阶段
// androidx/compose/ui/layout/Layout.kt
@Composable
inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    // ...
    
    // 测量策略
    val measureResult = measurePolicy.measure(
        measurables = children,
        constraints = constraints
    )
    
    // 布局策略
    measureResult.placeChildren()
}

// 自定义布局示例
@Composable
fun CustomLayout(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier
    ) { measurables, constraints ->
        
        // 测量子元素
        val placeables = measurables.map { measurable ->
            measurable.measure(constraints)
        }
        
        // 计算总尺寸
        val width = placeables.maxOf { it.width }
        val height = placeables.sumOf { it.height }
        
        // 布局
        layout(width, height) {
            var yPosition = 0
            placeables.forEach { placeable ->
                placeable.placeRelative(x = 0, y = yPosition)
                yPosition += placeable.height
            }
        }
    }
}
2.6.2 Drawing 阶段
// androidx/compose/ui/graphics/Canvas.kt
@Composable
fun CustomDrawing() {
    Canvas(modifier = Modifier.size(100.dp)) {
        // 绘制圆形
        drawCircle(
            color = Color.Red,
            radius = 50.dp.toPx()
        )
        
        // 绘制矩形
        drawRect(
            color = Color.Blue,
            topLeft = Offset(0f, 0f),
            size = Size(100.dp.toPx(), 100.dp.toPx())
        )
    }
}

// 使用 Modifier.drawBehind
@Composable
fun CustomBackground() {
    Box(
        modifier = Modifier
            .size(100.dp)
            .drawBehind {
                // 在内容后面绘制
                drawCircle(
                    color = Color.Red,
                    radius = size.minDimension / 2
                )
            }
    )
}

2.7 与 Choreographer 集成

Compose 最终也依赖 Choreographer 和 VSync 机制。

// androidx/compose/ui/platform/AndroidComposeView.kt
internal class AndroidComposeView(
    context: Context
) : ViewGroup(context), Owner {
    
    private val recomposer = Recomposer(Dispatchers.Main.immediate)
    
    // 请求重组和重绘
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        
        // 启动重组循环
        lifecycleScope.launch {
            recomposer.runRecomposeAndApplyChanges()
        }
    }
    
    // 调度帧
    private fun scheduleInvalidate() {
        if (!isLayoutRequested && isAttachedToWindow) {
            // 向 Choreographer 注册回调
            Choreographer.getInstance().postFrameCallback { frameTimeNanos ->
                // 1. 执行重组
                recomposer.recompose()
                
                // 2. 执行布局
                measureAndLayout()
                
                // 3. 触发绘制
                invalidate()  // 最终调用 View.invalidate()
            }
        }
    }
    
    // 测量和布局
    private fun measureAndLayout() {
        // Compose 的 Layout 测量
        root.measure(constraints)
        root.place(0, 0)
    }
    
    // 绘制
    override fun dispatchDraw(canvas: Canvas) {
        super.dispatchDraw(canvas)
        
        // 绘制 Compose UI
        canvasHolder.drawInto(canvas) {
            root.draw(this)
        }
    }
}

2.8 完整时间线

时间线 ────────────────────────────────────────────────────────────►

[T0] 用户点击按钮
     │
     ▼
     onClick { count++ }  (修改 State)
     │
     ▼
     MutableState.value = newValue
     │
     ▼
     Snapshot.writeObserver 触发
     │
     ▼
     recordModification()  标记状态已修改
     │
     ▼
     查找依赖此状态的 Composable
     │
     ▼
     标记这些 Composable 的 RecomposeScope 为 invalid
     │
     ▼
     Recomposer.scheduleRecompose(scope)
     │
     ▼
     添加到 snapshotInvalidations 队列
     │
     ▼
     Choreographer.postFrameCallback()
     │
     │ 【等待下一个 VSync 信号】
     │
[T1] ⏰ VSync 信号到达
     │
     ▼
     FrameCallback.doFrame(frameTimeNanos)
     │
     ├──► Snapshot.sendApplyNotifications()  应用快照变化
     │
     ├──► Recomposer.recompose()  【智能重组】
     │    │
     │    ├──► 遍历 snapshotInvalidations
     │    │
     │    ├──► 只重组 invalid 的 RecomposeScope
     │    │    (只重新执行读取了 count 的 Composable)
     │    │
     │    └──► 跳过没有依赖的 Composable
     │         (实现智能跳过,提高性能)
     │
     ├──► measureAndLayout()  【测量布局】
     │    │
     │    ├──► root.measure(constraints)
     │    └──► root.place(x, y)
     │
     └──► invalidate()  【触发绘制】
          │
          ▼
          View.invalidate()  (回到 View 体系)
          │
          ▼
          ViewRootImpl.scheduleTraversals()
          │
          ▼
          performDraw()
          │
          ▼
          canvas.draw()
          │
[T2] Surface.unlockCanvasAndPost()
     │
     ▼
     SurfaceFlinger 合成图层
     │
     ▼
[T3] 显示到屏幕

三、两者对比与总结

3.1 核心差异对比

特性View 体系Compose 体系
UI 范式命令式(Imperative)声明式(Declarative)
刷新触发手动调用 invalidate()状态变化自动触发
刷新范围整个 View 或子树智能识别变化部分
数据绑定手动更新 UI状态驱动,自动更新
UI 表示对象树(View 对象)Slot Table(扁平数组)
内存开销每个 View 是对象轻量级函数调用
性能优化onDraw() 优化、减少层级智能跳过、结构共享
线程模型必须主线程协程 + 主线程
VSync 依赖✅ 依赖 Choreographer✅ 最终也依赖 Choreographer
学习曲线陡峭(三大流程、测量模式)相对平缓(声明式直观)

3.2 代码对比

3.2.1 计数器示例

View 体系(命令式):

// XML 布局

    
    


// Activity 代码
class CounterActivity : AppCompatActivity() {
    private var count = 0
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_counter)
        
        val textView = findViewById(R.id.textView)
        val button = findViewById(R.id.button)
        
        // 手动更新 UI
        button.setOnClickListener {
            count++
            textView.text = "Count: $count"  // 命令式:告诉系统「怎么做」
            textView.invalidate()            // 手动触发刷新
        }
    }
}

Compose 体系(声明式):

@Composable
fun Counter() {
    // 状态驱动
    var count by remember { mutableStateOf(0) }
    
    // 声明式:告诉系统「是什么」
    Column {
        Text("Count: $count")  // 状态变化自动更新
        
        Button(onClick = { count++ }) {  // 修改状态
            Text("Increment")
        }
    }
    // 不需要手动调用 invalidate()
}
3.2.2 列表刷新

View 体系:

class UserListActivity : AppCompatActivity() {
    private val users = mutableListOf()
    private lateinit var adapter: UserAdapter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val recyclerView = findViewById(R.id.recyclerView)
        adapter = UserAdapter(users)
        recyclerView.adapter = adapter
        
        // 手动通知更新
        loadUsers { newUsers ->
            users.clear()
            users.addAll(newUsers)
            adapter.notifyDataSetChanged()  // 手动刷新
        }
    }
}

Compose 体系:

@Composable
fun UserList() {
    // 状态驱动
    var users by remember { mutableStateOf(listOf()) }
    
    // 声明式列表
    LazyColumn {
        items(users) { user ->
            UserItem(user)  // 状态变化自动更新
        }
    }
    
    // 加载数据
    LaunchedEffect(Unit) {
        users = loadUsers()  // 自动触发重组
    }
}

3.3 刷新机制流程对比

View 体系流程:
用户操作
   ↓
修改数据
   ↓
手动更新 UI (setText, setImageBitmap...)
   ↓
手动调用 invalidate()
   ↓
向上传递到 ViewRootImpl
   ↓
scheduleTraversals()
   ↓
Choreographer.postCallback(TRAVERSAL)
   ↓
等待 VSync
   ↓
执行三大流程 (Measure → Layout → Draw)
   ↓
提交到 SurfaceFlinger
   ↓
显示到屏幕
Compose 体系流程:
用户操作
   ↓
修改 State (count++)
   ↓
Snapshot.writeObserver 自动触发
   ↓
标记依赖的 Composable 为 invalid
   ↓
Recomposer.scheduleRecompose()
   ↓
Choreographer.postFrameCallback()
   ↓
等待 VSync
   ↓
智能重组 (只重组变化部分)
   ↓
Layout 阶段 (测量和布局)
   ↓
Drawing 阶段 (绘制)
   ↓
调用 View.invalidate() (回到 View 体系)
   ↓
提交到 SurfaceFlinger
   ↓
显示到屏幕

3.4 性能对比

性能指标View 体系Compose 体系
首次渲染快(已优化多年)略慢(额外的编译器处理)
增量更新慢(需要遍历整个树)快(智能跳过未变化部分)
内存占用高(每个 View 是对象)低(Slot Table 数组)
列表性能RecyclerView 复用LazyColumn 延迟组合
动画性能属性动画,性能好动画 API,性能接近
复杂布局层级嵌套影响性能ConstraintLayout 优化

3.5 适用场景

View 体系适合:
  • 现有项目维护
  • 需要精细控制绘制流程
  • 大量自定义 View
  • 对首次渲染性能要求极高
  • 团队不熟悉声明式编程
Compose 体系适合:
  • 新项目开发
  • 频繁的 UI 更新
  • 复杂的状态管理
  • 跨平台需求(Compose Multiplatform)
  • 追求代码简洁和可维护性

3.6 核心总结

View 体系的本质:
// 命令式:告诉系统「怎么做」
textView.text = "Hello"       // 步骤1:更新属性
textView.setTextColor(Color.RED)  // 步骤2:设置颜色
textView.invalidate()         // 步骤3:手动刷新
Compose 体系的本质:
// 声明式:告诉系统「是什么」
@Composable
fun Greeting(name: String) {
    Text(
        text = "Hello $name",
        color = Color.Red
    )
}
// 状态变化自动触发重组,UI 自动更新

3.7 最终结论

  1. 底层依赖相同:两者最终都依赖 VSync + Choreographer 机制
  2. 抽象层次不同:Compose 在 View 之上提供了更高层的声明式抽象
  3. 刷新策略不同
    • View:手动触发,全量刷新
    • Compose:状态驱动,智能增量刷新
  4. 互不替代:Compose 底层仍然基于 View,两者可以互操作
  5. 趋势方向:Compose 是 Google 推荐的现代 UI 工具包,代表未来方向

参考资料

源码位置

View 体系:

  • frameworks/base/core/java/android/view/View.java
  • frameworks/base/core/java/android/view/ViewRootImpl.java
  • frameworks/base/core/java/android/view/Choreographer.java

Compose 体系:

  • androidx/compose/runtime/
  • androidx/compose/ui/
  • androidx/compose/foundation/

推荐阅读

  1. Android 官方文档:View 和绘制机制
  2. Compose 官方文档:状态管理和重组
  3. 深入理解 Android 内核设计思想
  4. Jetpack Compose Internals(深入原理)

文章结束