引言
MVVM(Model-View-ViewModel)是 Android 开发中广泛采用的架构模式,它通过清晰的职责分离和数据绑定机制,帮助开发者构建可维护、可测试的应用程序。随着 Android Architecture Components 的推出,MVVM 模式在 Android 开发中变得更加重要和实用。本文将深入探讨 MVVM 架构模式的核心概念、实现方式以及最佳实践。
MVVM 架构概述
架构组成
MVVM 架构由三个主要组件组成:
- Model(模型):负责数据管理,包括网络请求、数据库操作、业务逻辑等
- View(视图):负责 UI 展示,包括 Activity、Fragment、Compose 等
- ViewModel(视图模型):连接 Model 和 View,管理 UI 相关数据和状态
// Model 层
data class User(
val id: String,
val name: String,
val email: String,
val avatar: String
)
interface UserRepository {
suspend fun getUsers(): List<User>
suspend fun getUserById(id: String): User?
suspend fun updateUser(user: User): Boolean
}
// ViewModel 层
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
fun loadUsers() {
viewModelScope.launch {
_isLoading.value = true
try {
val userList = repository.getUsers()
_users.value = userList
} catch (e: Exception) {
// 处理错误
} finally {
_isLoading.value = false
}
}
}
}
// View 层
class UserActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[UserViewModel::class.java]
observeViewModel()
viewModel.loadUsers()
}
private fun observeViewModel() {
viewModel.users.observe(this) { users ->
updateUserList(users)
}
viewModel.isLoading.observe(this) { isLoading ->
showLoading(isLoading)
}
}
}
核心组件详解
1. ViewModel
ViewModel 是 MVVM 架构的核心,它具有以下特点:
- 在配置更改时保持数据
- 管理 UI 相关的数据
- 不持有 View 的引用
- 具有生命周期感知能力
class ProductViewModel(
private val productRepository: ProductRepository,
private val cartRepository: CartRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(ProductUiState())
val uiState: StateFlow<ProductUiState> = _uiState.asStateFlow()
private val _events = Channel<ProductEvent>()
val events = _events.receiveAsFlow()
fun loadProducts(category: String) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
try {
val products = productRepository.getProductsByCategory(category)
_uiState.update {
it.copy(
products = products,
isLoading = false,
error = null
)
}
} catch (e: Exception) {
_uiState.update {
it.copy(
isLoading = false,
error = e.message
)
}
}
}
}
fun addToCart(product: Product) {
viewModelScope.launch {
try {
cartRepository.addProduct(product)
_events.send(ProductEvent.ProductAddedToCart(product.name))
} catch (e: Exception) {
_events.send(ProductEvent.Error(e.message ?: "Unknown error"))
}
}
}
fun searchProducts(query: String) {
viewModelScope.launch {
if (query.isBlank()) {
loadProducts("all")
return@launch
}
_uiState.update { it.copy(isLoading = true) }
try {
val products = productRepository.searchProducts(query)
_uiState.update {
it.copy(
products = products,
isLoading = false
)
}
} catch (e: Exception) {
_uiState.update {
it.copy(
isLoading = false,
error = e.message
)
}
}
}
}
}
data class ProductUiState(
val products: List<Product> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
sealed class ProductEvent {
data class ProductAddedToCart(val productName: String) : ProductEvent()
data class Error(val message: String) : ProductEvent()
}
2. Repository 模式
Repository 模式是 MVVM 架构中 Model 层的重要组成部分:
class UserRepositoryImpl(
private val apiService: ApiService,
private val userDao: UserDao,
private val networkManager: NetworkManager
) : UserRepository {
override suspend fun getUsers(): List<User> {
return if (networkManager.isConnected()) {
try {
val users = apiService.getUsers()
userDao.insertUsers(users)
users
} catch (e: Exception) {
// 网络失败时从本地获取
userDao.getAllUsers()
}
} else {
userDao.getAllUsers()
}
}
override suspend fun getUserById(id: String): User? {
return userDao.getUserById(id) ?: run {
if (networkManager.isConnected()) {
try {
val user = apiService.getUserById(id)
userDao.insertUser(user)
user
} catch (e: Exception) {
null
}
} else {
null
}
}
}
override suspend fun updateUser(user: User): Boolean {
return try {
if (networkManager.isConnected()) {
apiService.updateUser(user)
userDao.updateUser(user)
true
} else {
// 离线时标记为待同步
userDao.markForSync(user)
false
}
} catch (e: Exception) {
false
}
}
fun observeUsers(): Flow<List<User>> {
return userDao.observeUsers()
}
}
3. 数据绑定
使用 Data Binding 或 View Binding 简化 View 层代码:
// 使用 View Binding
class UserFragment : Fragment() {
private var _binding: FragmentUserBinding? = null
private val binding get() = _binding!!
private val viewModel: UserViewModel by viewModels()
private lateinit var userAdapter: UserAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentUserBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
observeViewModel()
setupSwipeRefresh()
viewModel.loadUsers()
}
private fun setupRecyclerView() {
userAdapter = UserAdapter { user ->
viewModel.selectUser(user)
}
binding.recyclerViewUsers.apply {
adapter = userAdapter
layoutManager = LinearLayoutManager(requireContext())
}
}
private fun observeViewModel() {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState.collect { state ->
updateUI(state)
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.events.collect { event ->
handleEvent(event)
}
}
}
private fun updateUI(state: UserUiState) {
binding.apply {
progressBar.isVisible = state.isLoading
recyclerViewUsers.isVisible = !state.isLoading && state.error == null
textViewError.isVisible = state.error != null
state.error?.let {
textViewError.text = it
}
userAdapter.submitList(state.users)
}
}
private fun setupSwipeRefresh() {
binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.refreshUsers()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
状态管理
使用 StateFlow 和 SharedFlow
class NewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
// UI 状态
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
// 一次性事件
private val _events = MutableSharedFlow<NewsEvent>()
val events: SharedFlow<NewsEvent> = _events.asSharedFlow()
// 用户操作
private val _userActions = MutableSharedFlow<UserAction>()
init {
handleUserActions()
loadNews()
}
private fun handleUserActions() {
viewModelScope.launch {
_userActions.collect { action ->
when (action) {
is UserAction.LoadNews -> loadNews(action.category)
is UserAction.RefreshNews -> refreshNews()
is UserAction.BookmarkNews -> bookmarkNews(action.newsId)
is UserAction.ShareNews -> shareNews(action.news)
}
}
}
}
fun submitAction(action: UserAction) {
viewModelScope.launch {
_userActions.emit(action)
}
}
private fun loadNews(category: String = "general") {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, error = null) }
newsRepository.getNews(category)
.catch { exception ->
_uiState.update {
it.copy(
isLoading = false,
error = exception.message
)
}
}
.collect { news ->
_uiState.update {
it.copy(
news = news,
isLoading = false,
error = null
)
}
}
}
}
private fun bookmarkNews(newsId: String) {
viewModelScope.launch {
try {
newsRepository.bookmarkNews(newsId)
_events.emit(NewsEvent.NewsBookmarked)
} catch (e: Exception) {
_events.emit(NewsEvent.Error(e.message ?: "Bookmark failed"))
}
}
}
}
data class NewsUiState(
val news: List<News> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
sealed class UserAction {
data class LoadNews(val category: String) : UserAction()
object RefreshNews : UserAction()
data class BookmarkNews(val newsId: String) : UserAction()
data class ShareNews(val news: News) : UserAction()
}
sealed class NewsEvent {
object NewsBookmarked : NewsEvent()
data class Error(val message: String) : NewsEvent()
}
依赖注入
使用 Hilt 进行依赖注入
// Application 类
@HiltAndroidApp
class MyApplication : Application()
// Module
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"app_database"
).build()
}
@Provides
fun provideUserDao(database: AppDatabase): UserDao {
return database.userDao()
}
}
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
@Provides
@Singleton
fun provideUserRepository(
apiService: ApiService,
userDao: UserDao
): UserRepository {
return UserRepositoryImpl(apiService, userDao)
}
}
// ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
// ViewModel 实现
}
// Activity/Fragment
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Activity 实现
}
}
测试策略
ViewModel 单元测试
class UserViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private val mockRepository = mockk<UserRepository>()
private lateinit var viewModel: UserViewModel
@Before
fun setup() {
viewModel = UserViewModel(mockRepository)
}
@Test
fun `loadUsers should update uiState with users when repository returns data`() = runTest {
// Given
val expectedUsers = listOf(
User("1", "John Doe", "john@example.com"),
User("2", "Jane Smith", "jane@example.com")
)
coEvery { mockRepository.getUsers() } returns expectedUsers
// When
viewModel.loadUsers()
// Then
val uiState = viewModel.uiState.value
assertEquals(expectedUsers, uiState.users)
assertFalse(uiState.isLoading)
assertNull(uiState.error)
}
@Test
fun `loadUsers should update uiState with error when repository throws exception`() = runTest {
// Given
val errorMessage = "Network error"
coEvery { mockRepository.getUsers() } throws RuntimeException(errorMessage)
// When
viewModel.loadUsers()
// Then
val uiState = viewModel.uiState.value
assertTrue(uiState.users.isEmpty())
assertFalse(uiState.isLoading)
assertEquals(errorMessage, uiState.error)
}
@Test
fun `searchUsers should filter users based on query`() = runTest {
// Given
val allUsers = listOf(
User("1", "John Doe", "john@example.com"),
User("2", "Jane Smith", "jane@example.com"),
User("3", "Bob Johnson", "bob@example.com")
)
val query = "John"
val expectedUsers = listOf(allUsers[0], allUsers[2])
coEvery { mockRepository.searchUsers(query) } returns expectedUsers
// When
viewModel.searchUsers(query)
// Then
val uiState = viewModel.uiState.value
assertEquals(expectedUsers, uiState.users)
}
}
Repository 测试
class UserRepositoryTest {
private val mockApiService = mockk<ApiService>()
private val mockUserDao = mockk<UserDao>()
private val mockNetworkManager = mockk<NetworkManager>()
private lateinit var repository: UserRepository
@Before
fun setup() {
repository = UserRepositoryImpl(mockApiService, mockUserDao, mockNetworkManager)
}
@Test
fun `getUsers should return data from API when network is available`() = runTest {
// Given
val apiUsers = listOf(User("1", "John", "john@example.com"))
every { mockNetworkManager.isConnected() } returns true
coEvery { mockApiService.getUsers() } returns apiUsers
coEvery { mockUserDao.insertUsers(any()) } just Runs
// When
val result = repository.getUsers()
// Then
assertEquals(apiUsers, result)
coVerify { mockUserDao.insertUsers(apiUsers) }
}
@Test
fun `getUsers should return cached data when network is unavailable`() = runTest {
// Given
val cachedUsers = listOf(User("1", "John", "john@example.com"))
every { mockNetworkManager.isConnected() } returns false
coEvery { mockUserDao.getAllUsers() } returns cachedUsers
// When
val result = repository.getUsers()
// Then
assertEquals(cachedUsers, result)
coVerify(exactly = 0) { mockApiService.getUsers() }
}
}
最佳实践
1. 单一职责原则
// 好的做法:职责分离
class UserProfileViewModel(
private val userRepository: UserRepository,
private val imageRepository: ImageRepository,
private val analyticsService: AnalyticsService
) : ViewModel() {
fun updateProfile(user: User) {
viewModelScope.launch {
try {
userRepository.updateUser(user)
analyticsService.trackEvent("profile_updated")
} catch (e: Exception) {
// 处理错误
}
}
}
fun uploadAvatar(imageUri: Uri) {
viewModelScope.launch {
try {
val imageUrl = imageRepository.uploadImage(imageUri)
userRepository.updateUserAvatar(imageUrl)
} catch (e: Exception) {
// 处理错误
}
}
}
}
2. 错误处理
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
}
class RobustViewModel(
private val repository: Repository
) : ViewModel() {
private val _uiState = MutableStateFlow<Result<List<Data>>>(Result.Loading)
val uiState: StateFlow<Result<List<Data>>> = _uiState.asStateFlow()
fun loadData() {
viewModelScope.launch {
_uiState.value = Result.Loading
try {
val data = repository.getData()
_uiState.value = Result.Success(data)
} catch (e: Exception) {
_uiState.value = Result.Error(e)
}
}
}
}
3. 内存泄漏预防
// 避免在 ViewModel 中持有 Context 引用
class BadViewModel(private val context: Context) : ViewModel() {
// 不好的做法:可能导致内存泄漏
}
// 好的做法:使用 Application Context 或依赖注入
@HiltViewModel
class GoodViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val repository: Repository
) : ViewModel() {
// 安全的做法
}
总结
MVVM 架构模式为 Android 开发提供了清晰的代码组织结构和职责分离。通过合理使用 ViewModel、Repository、数据绑定等组件,开发者可以构建出可维护、可测试、可扩展的应用程序。
关键要点包括:
- 清晰的职责分离:Model 负责数据,View 负责展示,ViewModel 负责逻辑
- 生命周期感知:使用 ViewModel 和 LiveData/StateFlow 处理配置更改
- 单向数据流:数据从 Model 流向 View,用户操作从 View 流向 ViewModel
- 依赖注入:使用 Hilt 等工具管理依赖关系
- 全面测试:为每个层级编写单元测试
掌握 MVVM 架构模式是现代 Android 开发的必备技能,它不仅能提高代码质量,还能显著提升开发效率和团队协作能力。
上一篇 下一篇