跳到主要内容

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 的核心优势

  1. 配置变更存活:屏幕旋转后 ViewModel 不重建,数据保留
  2. 生命周期感知viewModelScope 在 ViewModel 清除时自动取消协程
  3. 关注点分离:UI 逻辑和业务逻辑解耦

常见面试问题

Q1: ViewModel 是如何在配置变更后存活的?

答案

ViewModel 存储在 ViewModelStore 中,而 ViewModelStore 通过 ViewModelStoreOwner(即 Activity 的 NonConfigurationInstances)在配置变更时被系统保留。当 Activity 因旋转而重建时,新 Activity 从保留的 NonConfigurationInstances 中恢复 ViewModelStore,因此 ViewModel 实例不会被重新创建。

Q2: MVVM 中如何处理一次性事件(Toast、导航)?

答案

StateFlow 会重放最新值给新订阅者,不适合一次性事件。解决方案:

  1. SharedFlow(推荐)MutableSharedFlow<Event>(replay = 0) 不重放
  2. ChannelChannel<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 中,通过依赖注入传入。

相关链接