Android UI 刷新机制解析

Android UI 刷新机制深度解析

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

目录

  1. [传统 View 体系刷新机制](#传统 View 体系刷新机制 "#view-refresh")
  2. [Compose 体系刷新机制](#Compose 体系刷新机制 "#compose-refresh")
  3. 两者对比与总结

一、传统 View 体系刷新机制

1.1 核心组件架构

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

scss 复制代码
屏幕显示流程
    │
    ▼
┌──────────────┐
│  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/帧)

源码实现:

java 复制代码
// 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 内完成

回调类型优先级:

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

1.4 View 刷新流程

1.4.1 触发刷新

View.invalidate() 源码:

java 复制代码
// 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 向上传递刷新请求
java 复制代码
// 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 调度绘制
java 复制代码
// 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 完整流程
java 复制代码
// 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 测量
java 复制代码
// 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 布局
java 复制代码
// 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 绘制
java 复制代码
// 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)来避免画面闪烁和撕裂。

原理:

scss 复制代码
帧 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 完整时间线

scss 复制代码
时间线 ────────────────────────────────────────────────────────────►

[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 体系有本质区别。

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

2.2 State(状态管理)

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

2.2.1 State 接口
kotlin 复制代码
// androidx/compose/runtime/State.kt
@Stable
interface State<out T> {
    val value: T
}

// androidx/compose/runtime/MutableState.kt
interface MutableState<T> : State<T> {
    override var value: T
    operator fun component1(): T
    operator fun component2(): (T) -> Unit
}
2.2.2 创建和使用 State
kotlin 复制代码
// 基本用法
@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 原理
kotlin 复制代码
// 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 状态读写拦截
kotlin 复制代码
// 读取状态时
fun <T> State<T>.getValue(): T {
    // 通知当前快照:这个 Composable 依赖此状态
    Snapshot.current.readObserver?.invoke(this)
    return this.value
}

// 修改状态时
fun <T> MutableState<T>.setValue(value: T) {
    // 通知快照系统:状态已修改
    Snapshot.current.writeObserver?.invoke(this)
    this.value = value
    
    // 触发重组
    scheduleRecomposition()
}
2.3.3 实际运行示例
kotlin 复制代码
@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 源码
kotlin 复制代码
// androidx/compose/runtime/Recomposer.kt
class Recomposer(
    effectCoroutineContext: CoroutineContext
) : CompositionContext() {
    
    // 需要重组的作用域列表
    private val snapshotInvalidations = mutableListOf<RecomposeScopeImpl>()
    
    // 调度重组
    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 编译器会自动识别重组边界,只重组受影响的部分。

kotlin 复制代码
@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 编译器生成的代码
kotlin 复制代码
// 编译前
@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 那样创建对象树。

kotlin 复制代码
// androidx/compose/runtime/SlotTable.kt
internal class SlotTable {
    // 扁平化的数组存储
    var slots: Array<Any?> = 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 阶段
kotlin 复制代码
// 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 阶段
kotlin 复制代码
// 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 机制。

kotlin 复制代码
// 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 完整时间线

scss 复制代码
时间线 ────────────────────────────────────────────────────────────►

[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 体系(命令式):

kotlin 复制代码
// XML 布局
<LinearLayout>
    <TextView
        android:id="@+id/textView"
        android:text="Count: 0" />
    <Button
        android:id="@+id/button"
        android:text="Increment" />
</LinearLayout>

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

Compose 体系(声明式):

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

View 体系:

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

Compose 体系:

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

3.3 刷新机制流程对比

View 体系流程:
scss 复制代码
用户操作
   ↓
修改数据
   ↓
手动更新 UI (setText, setImageBitmap...)
   ↓
手动调用 invalidate()
   ↓
向上传递到 ViewRootImpl
   ↓
scheduleTraversals()
   ↓
Choreographer.postCallback(TRAVERSAL)
   ↓
等待 VSync
   ↓
执行三大流程 (Measure → Layout → Draw)
   ↓
提交到 SurfaceFlinger
   ↓
显示到屏幕
Compose 体系流程:
scss 复制代码
用户操作
   ↓
修改 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 体系的本质:
kotlin 复制代码
// 命令式:告诉系统「怎么做」
textView.text = "Hello"       // 步骤1:更新属性
textView.setTextColor(Color.RED)  // 步骤2:设置颜色
textView.invalidate()         // 步骤3:手动刷新
Compose 体系的本质:
kotlin 复制代码
// 声明式:告诉系统「是什么」
@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(深入原理)

文章结束

相关推荐
Bigger23 分钟前
🚀 “踩坑日记”:shadcn + Vite 在 Monorepo 中配置报错
前端·react.js·vite
冬男zdn2 小时前
优雅处理数组的几个实用方法
前端·javascript
Kaze_story2 小时前
Vue第四节:组件化、组件生命周期
前端·javascript·vue.js
yuzhiboyouye2 小时前
web前端开发自测清单
前端
妮妮分享2 小时前
H5获取定位的方式是什么?
java·前端·javascript
weixin_439930642 小时前
前端js日期计算跨月导致的错误
开发语言·前端·javascript
零一科技3 小时前
瑞吉外卖项目,前端源码(用户端)解析
前端
用户93051065822243 小时前
module federation,monorepo分不清楚?
前端·架构
柳安3 小时前
手写new操作符执行过程
前端·javascript
狗哥哥3 小时前
Vue 3 统一面包屑导航系统:从配置地狱到单一数据源
前端·vue.js·架构