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() 恢复。
// 获取 ViewModel
val viewModel: MyViewModel by viewModels()
// 共享 ViewModel(Fragment 间共享)
val sharedViewModel: SharedViewModel by activityViewModels()1.2 SavedStateHandle
ViewModel 在进程被杀死后会丢失数据。SavedStateHandle 将数据保存到 Bundle 中,进程恢复时可以恢复:
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 组件让任何类都能感知生命周期:
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 → DESTROYED3. LiveData
3.1 基本原理
LiveData 是生命周期感知的可观察数据容器:
val liveData = MutableLiveData<String>()
// 观察(自动在 STARTED 以上状态分发)
liveData.observe(lifecycleOwner) { value ->
textView.text = value
}
// 更新
liveData.value = "新数据" // 主线程
liveData.postValue("新数据") // 任意线程3.2 源码分析
核心是版本号机制:
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、导航)。
解决方案:
- 用
SharedFlow(replay=0)替代(推荐) - Event 包装类(SingleLiveEvent)
- 反射修改 observer 的 mLastVersion
4. Room
4.1 核心组件
// 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
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 序列
// 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 对比
| 维度 | MVVM | MVI |
|---|---|---|
| 数据流 | 多个 LiveData/Flow | 单一 State |
| 状态管理 | 分散在多个可观察对象 | 集中在一个 State |
| 可预测性 | 一般 | 强(单向数据流) |
| 复杂度 | 较低 | 较高(需要定义 Intent/State) |
| 适用场景 | 简单页面 | 复杂交互、需要状态回溯 |
6. Jetpack Compose
6.1 声明式 UI 原理
Compose 使用声明式范式:描述 UI 应该是什么样子,而非如何操作 UI。
@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 函数:
@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 编译器会推断类型的稳定性,决定是否可以跳过重组:
// 稳定类型:所有属性都是 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.immutable 的 ImmutableList。
6.6 副作用 API
// 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 性能优化
- 减少重组范围:将读取 State 的代码下推到最小的 Composable
- 使用 key:
LazyColumn中为 item 指定稳定的 key - 延迟读取:传递 lambda 而非值
Modifier.offset { IntOffset(x, 0) } - remember:缓存计算结果
- derivedStateOf:避免中间状态触发重组
- @Stable/@Immutable:帮助编译器跳过重组
7. Navigation
7.1 核心组件
- NavHost:容器,显示当前目的地(Fragment 或 Composable)
- NavController:控制导航,管理回退栈
- NavGraph:导航图,定义所有目的地和路径
// 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
类型安全的参数传递(编译期检查):
// 传递
val action = HomeFragmentDirections.actionHomeToDetail(userId = 123)
findNavController().navigate(action)
// 接收
val args: DetailFragmentArgs by navArgs()
val userId = args.userId8. Paging 3
分页加载库,自动处理加载更多、错误重试、刷新:
// 数据源
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
@Composable
fun MapView() {
AndroidView(
factory = { context ->
MapView(context).apply { onCreate(null) }
},
update = { mapView ->
// State 变化时更新 View
}
)
}9.2 在 View 中使用 Compose
// 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 渐进式迁移策略
- 新页面用 Compose 写
- 旧页面中的新组件用 ComposeView 嵌入
- 逐步将旧 Fragment 迁移为 Composable
- 最终移除 Fragment 和 XML 布局
