Skip to content

Jetpack / MVVM / Compose — 知识详解

更新: 5/15/2026 字数: 0 字 时长: 0 分钟

Compose 原理内容较多,已拆分为独立文件:

  • compose-deep.md — Compose 原理深入(编译器变换、Slot Table、重组机制、Snapshot 系统、副作用 API、性能优化)

1. ViewModel

1.1 作用与原理

ViewModel 用于存储和管理 UI 相关数据,在配置变更(如旋转屏幕)时不会被销毁。

存储原理

Activity / Fragment
  └── ViewModelStoreOwner
        └── ViewModelStore(HashMap<String, ViewModel>)
              ├── "MyViewModel" → MyViewModel 实例
              └── "SharedViewModel" → SharedViewModel 实例

配置变更时,Activity 通过 onRetainNonConfigurationInstance() 保存 ViewModelStore 对象。新 Activity 创建后通过 getLastNonConfigurationInstance() 恢复。

kotlin
// 获取 ViewModel
val viewModel: MyViewModel by viewModels()

// 共享 ViewModel(Fragment 间共享)
val sharedViewModel: SharedViewModel by activityViewModels()

1.2 SavedStateHandle

ViewModel 在进程被杀死后会丢失数据。SavedStateHandle 将数据保存到 Bundle 中,进程恢复时可以恢复:

kotlin
class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val query: LiveData<String> = savedStateHandle.getLiveData("query", "")

    fun setQuery(q: String) {
        savedStateHandle["query"] = q // 自动保存到 Bundle
    }
}

1.3 生命周期

ViewModel 的 onCleared() 在 Activity finish 或 Fragment detach 时调用(不是配置变更)。适合在这里取消网络请求、关闭数据库连接等。


2. Lifecycle

2.1 观察者模式

Lifecycle 组件让任何类都能感知生命周期:

kotlin
class MyObserver : DefaultLifecycleObserver {
    override fun onResume(owner: LifecycleOwner) {
        // 开始监听
    }
    override fun onPause(owner: LifecycleOwner) {
        // 停止监听
    }
}

// 注册
lifecycle.addObserver(MyObserver())

2.2 实现原理

Activity/Fragment 实现了 LifecycleOwner 接口,内部持有 LifecycleRegistry

在 Activity 中,通过注入一个无 UI 的 ReportFragment 来监听生命周期回调,然后分发给 LifecycleRegistry。

状态与事件的关系:

INITIALIZED → ON_CREATE → CREATED → ON_START → STARTED → ON_RESUME → RESUMED
RESUMED → ON_PAUSE → STARTED → ON_STOP → CREATED → ON_DESTROY → DESTROYED

3. LiveData

3.1 基本原理

LiveData 是生命周期感知的可观察数据容器:

kotlin
val liveData = MutableLiveData<String>()

// 观察(自动在 STARTED 以上状态分发)
liveData.observe(lifecycleOwner) { value ->
    textView.text = value
}

// 更新
liveData.value = "新数据"          // 主线程
liveData.postValue("新数据")       // 任意线程

3.2 源码分析

核心是版本号机制:

java
public class LiveData<T> {
    private int mVersion = START_VERSION; // -1
    private T mData;

    @MainThread
    protected void setValue(T value) {
        mVersion++;          // 版本号+1
        mData = value;
        dispatchingValue(null); // 通知所有观察者
    }

    private void considerNotify(ObserverWrapper observer) {
        // 观察者不活跃则跳过
        if (!observer.mActive) return;
        // 观察者的版本号 >= LiveData 版本号,说明已经收到过,跳过
        if (observer.mLastVersion >= mVersion) return;
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged(mData); // 通知
    }
}

3.3 粘性事件问题

新观察者注册时,如果 observer.mLastVersion(-1) < mVersion,会立即收到最后一次数据。这在状态场景下是正确的(如 UI 状态恢复),但在事件场景下会导致重复消费(如 Toast、导航)。

解决方案:

  1. SharedFlow(replay=0) 替代(推荐)
  2. Event 包装类(SingleLiveEvent)
  3. 反射修改 observer 的 mLastVersion

4. Room

4.1 核心组件

kotlin
// Entity:数据表
@Entity(tableName = "users")
data class User(
    @PrimaryKey val id: Int,
    @ColumnInfo(name = "user_name") val name: String,
    val age: Int
)

// DAO:数据访问接口
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUser(userId: Int): User?

    @Query("SELECT * FROM users")
    fun getAllUsers(): Flow<List<User>> // 数据变化时自动通知

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(user: User)

    @Delete
    suspend fun delete(user: User)
}

// Database
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

4.2 编译期处理

Room 使用 APT 在编译期生成 DAO 的实现类。@Query 中的 SQL 在编译期就会验证语法和表名/列名是否正确,避免运行时崩溃。

4.3 Migration

kotlin
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("ALTER TABLE users ADD COLUMN email TEXT NOT NULL DEFAULT ''")
    }
}

Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .addMigrations(MIGRATION_1_2)
    .build()

5. MVVM vs MVI

5.1 MVVM

View ←(observe)← ViewModel ←→ Repository ←→ DataSource
  • View 观察 ViewModel 的 LiveData/StateFlow
  • ViewModel 处理业务逻辑,调用 Repository
  • 双向数据绑定(DataBinding)或单向观察

5.2 MVI

View →(Intent)→ ViewModel →(State)→ View
  • 单向数据流:View 发送 Intent(用户意图)→ ViewModel 处理 → 产生新 State → View 渲染
  • 状态不可变:每次产生全新的 State 对象
  • 可预测:相同的 Intent 序列产生相同的 State 序列
kotlin
// State
data class MainState(
    val isLoading: Boolean = false,
    val items: List<Item> = emptyList(),
    val error: String? = null
)

// Intent
sealed class MainIntent {
    object LoadItems : MainIntent()
    data class DeleteItem(val id: Int) : MainIntent()
}

// ViewModel
class MainViewModel : ViewModel() {
    private val _state = MutableStateFlow(MainState())
    val state: StateFlow<MainState> = _state.asStateFlow()

    fun handleIntent(intent: MainIntent) {
        when (intent) {
            is MainIntent.LoadItems -> {
                _state.update { it.copy(isLoading = true) }
                viewModelScope.launch {
                    val items = repository.getItems()
                    _state.update { it.copy(isLoading = false, items = items) }
                }
            }
            is MainIntent.DeleteItem -> { /* ... */ }
        }
    }
}

5.3 对比

维度MVVMMVI
数据流多个 LiveData/Flow单一 State
状态管理分散在多个可观察对象集中在一个 State
可预测性一般强(单向数据流)
复杂度较低较高(需要定义 Intent/State)
适用场景简单页面复杂交互、需要状态回溯

6. Jetpack Compose

6.1 声明式 UI 原理

Compose 使用声明式范式:描述 UI 应该是什么样子,而非如何操作 UI。

kotlin
@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")  // 描述 UI,不是命令式操作
}

6.2 Slot Table 与 Gap Buffer

Compose 编译器将 @Composable 函数转换为对 Slot Table 的操作。Slot Table 使用 Gap Buffer 数据结构存储组合树:

  • Slot Table 存储所有 Composable 的状态和参数
  • Gap Buffer 使得插入/删除操作高效(类似文本编辑器的实现)
  • 重组时,Compose 比较新旧参数,只更新变化的部分

6.3 重组(Recomposition)

当 State 变化时,Compose 只重新执行读取了该 State 的 Composable 函数:

kotlin
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }  // State
    Button(onClick = { count++ }) {
        Text("Count: $count")  // 只有这个 Text 会重组
    }
}

重组作用域:Compose 编译器在每个 Composable 函数调用处插入重组作用域。State 变化时,只有包含该 State 读取的最小作用域会重组。

6.4 Snapshot 系统

Compose 的状态管理基于 Snapshot 系统:

  • mutableStateOf 创建的 State 对象会被 Snapshot 系统追踪
  • 读取 State 时注册依赖关系
  • 写入 State 时通知所有依赖的重组作用域

6.5 稳定性推断

Compose 编译器会推断类型的稳定性,决定是否可以跳过重组:

kotlin
// 稳定类型:所有属性都是 val 且类型稳定
data class User(val name: String, val age: Int) // ✅ 稳定

// 不稳定类型:有 var 属性或不稳定类型
data class User(var name: String) // ❌ 不稳定
data class State(val items: List<Item>) // ❌ List 接口不稳定(可能是 MutableList)

@Stable@Immutable 注解手动标记稳定性。或使用 kotlinx.collections.immutableImmutableList

6.6 副作用 API

kotlin
// LaunchedEffect:进入组合时启动协程,key 变化时重启
LaunchedEffect(userId) {
    val user = repository.getUser(userId)
    // ...
}

// DisposableEffect:需要清理的副作用
DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event -> /* ... */ }
    lifecycleOwner.lifecycle.addObserver(observer)
    onDispose {
        lifecycleOwner.lifecycle.removeObserver(observer)
    }
}

// rememberCoroutineScope:获取与组合绑定的 CoroutineScope
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { /* ... */ } })

// derivedStateOf:派生状态,减少不必要的重组
val filteredList by remember {
    derivedStateOf { list.filter { it.isActive } }
}

// snapshotFlow:将 Compose State 转为 Flow
LaunchedEffect(Unit) {
    snapshotFlow { scrollState.firstVisibleItemIndex }
        .collect { index -> /* ... */ }
}

6.7 Compose 性能优化

  1. 减少重组范围:将读取 State 的代码下推到最小的 Composable
  2. 使用 keyLazyColumn 中为 item 指定稳定的 key
  3. 延迟读取:传递 lambda 而非值 Modifier.offset { IntOffset(x, 0) }
  4. remember:缓存计算结果
  5. derivedStateOf:避免中间状态触发重组
  6. @Stable/@Immutable:帮助编译器跳过重组

7. Navigation

7.1 核心组件

  • NavHost:容器,显示当前目的地(Fragment 或 Composable)
  • NavController:控制导航,管理回退栈
  • NavGraph:导航图,定义所有目的地和路径
kotlin
// Compose Navigation
NavHost(navController = navController, startDestination = "home") {
    composable("home") { HomeScreen(navController) }
    composable("detail/{id}") { backStackEntry ->
        val id = backStackEntry.arguments?.getString("id")
        DetailScreen(id)
    }
}

// 导航
navController.navigate("detail/123")

// 带参数和选项
navController.navigate("detail/123") {
    popUpTo("home") { inclusive = false } // 弹出到 home
    launchSingleTop = true               // 避免重复创建
}

7.2 Safe Args

类型安全的参数传递(编译期检查):

kotlin
// 传递
val action = HomeFragmentDirections.actionHomeToDetail(userId = 123)
findNavController().navigate(action)

// 接收
val args: DetailFragmentArgs by navArgs()
val userId = args.userId

8. Paging 3

分页加载库,自动处理加载更多、错误重试、刷新:

kotlin
// 数据源
class UserPagingSource(private val api: ApiService) : PagingSource<Int, User>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
        val page = params.key ?: 1
        return try {
            val response = api.getUsers(page, params.loadSize)
            LoadResult.Page(
                data = response.users,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (response.users.isEmpty()) null else page + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

// ViewModel
val users: Flow<PagingData<User>> = Pager(PagingConfig(pageSize = 20)) {
    UserPagingSource(api)
}.flow.cachedIn(viewModelScope)

// UI (Compose)
val lazyPagingItems = viewModel.users.collectAsLazyPagingItems()
LazyColumn {
    items(lazyPagingItems) { user ->
        UserItem(user)
    }
}

9. Compose 与 View 互操作

9.1 在 Compose 中使用 View

kotlin
@Composable
fun MapView() {
    AndroidView(
        factory = { context ->
            MapView(context).apply { onCreate(null) }
        },
        update = { mapView ->
            // State 变化时更新 View
        }
    )
}

9.2 在 View 中使用 Compose

kotlin
// XML 中添加 ComposeView
<androidx.compose.ui.platform.ComposeView
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

// 代码中设置内容
composeView.setContent {
    MaterialTheme {
        MyComposable()
    }
}

9.3 渐进式迁移策略

  1. 新页面用 Compose 写
  2. 旧页面中的新组件用 ComposeView 嵌入
  3. 逐步将旧 Fragment 迁移为 Composable
  4. 最终移除 Fragment 和 XML 布局