MVVM 架构
问题
MVVM 架构在 Android 中是如何实现的?
答案
MVVM 数据流
核心原则:View 观察 ViewModel 的数据变化,ViewModel 不持有 View 的引用。
完整 MVVM 示例
// 1. Data Layer - Entity
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: String,
val name: String,
val email: String
)
// 2. Data Layer - Repository
class UserRepository(
private val api: ApiService,
private val dao: UserDao
) {
fun getUser(id: String): Flow<User> = flow {
// 先返回本地缓存
dao.getUser(id)?.let { emit(it) }
// 再从网络获取
try {
val user = api.getUser(id)
dao.insertUser(user)
emit(user)
} catch (e: Exception) {
// 网络错误时仅使用本地数据
}
}
}
// 3. ViewModel
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(id: String) {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
repository.getUser(id)
.catch { e -> _uiState.value = UserUiState.Error(e.message ?: "") }
.collect { user -> _uiState.value = UserUiState.Success(user) }
}
}
}
sealed class UserUiState {
data object Loading : UserUiState()
data class Success(val user: User) : UserUiState()
data class Error(val message: String) : UserUiState()
}
// 4. View (Fragment)
class UserFragment : Fragment() {
private val viewModel: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UserUiState.Loading -> showLoading()
is UserUiState.Success -> showUser(state.user)
is UserUiState.Error -> showError(state.message)
}
}
}
}
viewModel.loadUser("123")
}
}
ViewModel 的核心优势
- 配置变更存活:屏幕旋转后 ViewModel 不重建,数据保留
- 生命周期感知:
viewModelScope在 ViewModel 清除时自动取消协程 - 关注点分离:UI 逻辑和业务逻辑解耦
常见面试问题
Q1: ViewModel 是如何在配置变更后存活的?
答案:
ViewModel 存储在 ViewModelStore 中,而 ViewModelStore 通过 ViewModelStoreOwner(即 Activity 的 NonConfigurationInstances)在配置变更时被系统保留。当 Activity 因旋转而重建时,新 Activity 从保留的 NonConfigurationInstances 中恢复 ViewModelStore,因此 ViewModel 实例不会被重新创建。
Q2: MVVM 中如何处理一次性事件(Toast、导航)?
答案:
StateFlow 会重放最新值给新订阅者,不适合一次性事件。解决方案:
- SharedFlow(推荐):
MutableSharedFlow<Event>(replay = 0)不重放 - Channel:
Channel<Event>(Channel.BUFFERED)+receiveAsFlow()
class UserViewModel : ViewModel() {
private val _events = MutableSharedFlow<UiEvent>()
val events = _events.asSharedFlow()
fun onSave() {
viewModelScope.launch {
repository.save()
_events.emit(UiEvent.ShowToast("保存成功"))
}
}
}
Q3: ViewModel 可以持有 Context 吗?
答案:
不能持有 Activity Context(内存泄漏)。如需 Context,使用 AndroidViewModel 持有 Application Context:
class MyViewModel(application: Application) : AndroidViewModel(application) {
private val context get() = getApplication<Application>()
}
更好的做法是将需要 Context 的操作放在 Repository 或 DataSource 中,通过依赖注入传入。