Android UI 刷新机制深度解析
注:本文是记录学习过程。
目录
- [传统 View 体系刷新机制](#传统 View 体系刷新机制 "#view-refresh")
- [Compose 体系刷新机制](#Compose 体系刷新机制 "#compose-refresh")
- 两者对比与总结
一、传统 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 引入的核心类,负责协调动画、输入和绘制的时机。
核心职责:
- 接收 VSync 信号
- 管理四种类型的回调任务
- 按优先级执行回调
- 保证渲染在 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 到达时交换缓冲区
流程说明:
- CPU/GPU 绘制到 Back Buffer(后台缓冲区)
- VSync 信号到达
- 交换 Front Buffer 和 Back Buffer
- 显示器读取 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 最终结论
- 底层依赖相同:两者最终都依赖 VSync + Choreographer 机制
- 抽象层次不同:Compose 在 View 之上提供了更高层的声明式抽象
- 刷新策略不同 :
- View:手动触发,全量刷新
- Compose:状态驱动,智能增量刷新
- 互不替代:Compose 底层仍然基于 View,两者可以互操作
- 趋势方向:Compose 是 Google 推荐的现代 UI 工具包,代表未来方向
参考资料
源码位置
View 体系:
frameworks/base/core/java/android/view/View.javaframeworks/base/core/java/android/view/ViewRootImpl.javaframeworks/base/core/java/android/view/Choreographer.java
Compose 体系:
androidx/compose/runtime/androidx/compose/ui/androidx/compose/foundation/
推荐阅读
- Android 官方文档:View 和绘制机制
- Compose 官方文档:状态管理和重组
- 深入理解 Android 内核设计思想
- Jetpack Compose Internals(深入原理)
文章结束