[安卓] Kotlin中的架构演进:从MVC到MVVM
从Java到Kotlin,不仅仅是语法的升级,更是架构思维的进化。本文将带你全面掌握Kotlin在Android开发中三种核心架构模式:MVC、MVP、MVVM,揭示如何用Kotlin特性写出更优雅的架构代码。
引言:为什么Android开发者需要架构模式?
如果你有过Java Android开发经验,一定遇到过这些问题:Activity/Fragment代码臃肿(动辄上千行)、业务逻辑与UI逻辑混杂、测试困难、代码难以维护。架构模式正是为了解决这些问题而生的。而Kotlin,以其简洁的语法和强大的特性,让这些架构模式的实现变得更加优雅。
让我们从一个真实案例开始——用户登录模块,看看在不同架构模式下的演变:
// 糟糕的无架构代码(Java风格)class BadLoginActivity :AppCompatActivity(){privatelateinitvar editUsername: EditText privatelateinitvar editPassword: EditText privatelateinitvar buttonLogin: Button overridefunonCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_login) editUsername =findViewById(R.id.edit_username) editPassword =findViewById(R.id.edit_password) buttonLogin =findViewById(R.id.button_login) buttonLogin.setOnClickListener{// 1. UI逻辑val username = editUsername.text.toString()val password = editPassword.text.toString()if(username.isEmpty()|| password.isEmpty()){ Toast.makeText(this,"用户名或密码不能为空", Toast.LENGTH_SHORT).show()return@setOnClickListener}// 2. 业务逻辑val isValid =validateCredentials(username, password)if(!isValid){ Toast.makeText(this,"用户名或密码格式错误", Toast.LENGTH_SHORT).show()return@setOnClickListener}// 3. 网络请求showLoading() Thread {// 模拟网络请求 Thread.sleep(2000) runOnUiThread {hideLoading()if(username =="admin"&& password =="123456"){ Toast.makeText(this,"登录成功", Toast.LENGTH_SHORT).show()startActivity(Intent(this, MainActivity::class.java))}else{ Toast.makeText(this,"用户名或密码错误", Toast.LENGTH_SHORT).show()}}}.start()}}privatefunvalidateCredentials(username: String, password: String): Boolean {return username.length >=3&& password.length >=6}privatefunshowLoading(){/* 显示加载动画 */}privatefunhideLoading(){/* 隐藏加载动画 */}}这样的代码有太多问题:职责不清、难以测试、回调地狱。接下来,让我们看看如何用架构模式重构它。
一、MVC模式:最基础的分层架构
1.1 MVC概念解析
MVC(Model-View-Controller) 是最经典的架构模式,将应用分为三层:
- Model(模型):数据和业务逻辑
- View(视图):UI展示
- Controller(控制器):处理用户输入,协调Model和View
在Android中,通常:
- View = XML布局 + Activity/Fragment(视图部分)
- Controller = Activity/Fragment(控制逻辑部分)
- Model = 数据类、仓库、业务逻辑类
// Kotlin MVC实现示例// ==================== Model层 ====================dataclassUser(val username: String,val password: String)class LoginModel {// 业务逻辑funvalidateCredentials(username: String, password: String): Boolean {return username.length >=3&& password.length >=6}// 模拟网络请求suspendfunlogin(username: String, password: String): Boolean {delay(2000)// 模拟网络延迟return username =="admin"&& password =="123456"}}// ==================== View层 ====================// activity_login.xml (UI布局)// ==================== Controller层 ====================classLoginController(privateval view: LoginView){privateval model =LoginModel()funonLoginClicked(username: String, password: String){// 1. 验证输入if(username.isEmpty()|| password.isEmpty()){ view.showError("用户名或密码不能为空")return}if(!model.validateCredentials(username, password)){ view.showError("用户名或密码格式错误")return}// 2. 执行登录 view.showLoading()// 使用协程处理异步 view.launchOnLifecycle{try{val success = model.login(username, password)if(success){ view.onLoginSuccess()}else{ view.showError("用户名或密码错误")}}catch(e: Exception){ view.showError("网络错误: ${e.message}")}finally{ view.hideLoading()}}}}interface LoginView {funshowLoading()funhideLoading()funshowError(message: String)funonLoginSuccess()funlaunchOnLifecycle(block:suspend()-> Unit)}// ==================== Activity(View+Controller混合)====================class LoginActivity :AppCompatActivity(), LoginView {privatelateinitvar controller: LoginController privatelateinitvar binding: ActivityLoginBinding overridefunonCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState) binding = ActivityLoginBinding.inflate(layoutInflater)setContentView(binding.root) controller =LoginController(this) binding.buttonLogin.setOnClickListener{val username = binding.editUsername.text.toString()val password = binding.editPassword.text.toString() controller.onLoginClicked(username, password)}}overridefunshowLoading(){ binding.progressBar.visibility = View.VISIBLE binding.buttonLogin.isEnabled =false}overridefunhideLoading(){ binding.progressBar.visibility = View.GONE binding.buttonLogin.isEnabled =true}overridefunshowError(message: String){ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()}overridefunonLoginSuccess(){ Toast.makeText(this,"登录成功", Toast.LENGTH_SHORT).show()startActivity(Intent(this, MainActivity::class.java))finish()}overridefunlaunchOnLifecycle(block:suspend()-> Unit){ lifecycleScope.launch{block()}}}1.2 MVC的优缺点分析
优点:
- 结构清晰,简单易懂
- 分离了数据和展示
- 适合小型项目
缺点:
- Activity/Fragment既是View又是Controller,容易变得臃肿
- View和Model有直接或间接耦合
- 单元测试困难
Kotlin带来的改进:
- 协程简化异步操作,避免回调地狱
- 扩展函数可以抽离通用UI逻辑
- 数据类简化Model定义
二、MVP模式:关注点分离的进化
2.1 MVP概念解析
MVP(Model-View-Presenter) 是对MVC的改进,核心变化:
- Presenter 替代Controller,负责所有业务逻辑
- View 只负责UI展示,变成被动接口
- Model 保持不变
关键特点:View和Model完全解耦,通过Presenter通信。
// Kotlin MVP实现示例// ==================== Contract(契约接口)====================// 使用Kotlin的接口默认方法interface LoginContract {interface View {funshowLoading()funhideLoading()funshowError(message: String)funonLoginSuccess()fungetUsername(): String fungetPassword(): String funclearInputs()}interface Presenter {funattachView(view: View)fundetachView()funonLoginClicked()funvalidateInput(): Boolean }}// ==================== Model层 ====================dataclassUser(val id: String,val name: String,val token: String)interface UserRepository {suspendfunlogin(username: String, password: String): Result<User>}class UserRepositoryImpl : UserRepository {overridesuspendfunlogin(username: String, password: String): Result<User>{delay(2000)// 模拟网络延迟returnif(username =="admin"&& password =="123456"){ Result.success(User("001","管理员","token_abc123"))}else{ Result.failure(Exception("用户名或密码错误"))}}}// ==================== Presenter层 ====================classLoginPresenter(privateval repository: UserRepository,privateval dispatcher: CoroutineDispatcher = Dispatchers.Main ): LoginContract.Presenter{privatevar view: LoginContract.View?=nullprivatevar job: Job?=nulloverridefunattachView(view: LoginContract.View){this.view = view }overridefundetachView(){ job?.cancel() view =null}overridefunonLoginClicked(){// 验证输入if(!validateInput()){return}val view =this.view ?:returnval username = view.getUsername()val password = view.getPassword()// 显示加载 view.showLoading()// 启动协程 job =CoroutineScope(dispatcher).launch{try{val result =withContext(Dispatchers.IO){ repository.login(username, password)}handleLoginResult(result)}catch(e: Exception){ view?.showError("网络错误: ${e.message}")}finally{ view?.hideLoading()}}}overridefunvalidateInput(): Boolean {val view =this.view ?:returnfalseval username = view.getUsername()val password = view.getPassword()returnwhen{ username.isEmpty()->{ view.showError("用户名不能为空")false} password.isEmpty()->{ view.showError("密码不能为空")false} username.length <3->{ view.showError("用户名至少3个字符")false} password.length <6->{ view.showError("密码至少6个字符")false}else->true}}privatefunhandleLoginResult(result: Result<User>){val view =this.view ?:return result.fold( onSuccess ={ user ->// 登录成功 view.onLoginSuccess() view.clearInputs()// 可以保存用户信息等操作}, onFailure ={ error -> view.showError(error.message ?:"登录失败")})}}// ==================== View层 ====================class LoginActivity :AppCompatActivity(), LoginContract.View{privatelateinitvar presenter: LoginPresenter privatelateinitvar binding: ActivityLoginBinding overridefunonCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState) binding = ActivityLoginBinding.inflate(layoutInflater)setContentView(binding.root)// 初始化Presenterval repository =UserRepositoryImpl() presenter =LoginPresenter(repository) presenter.attachView(this) binding.buttonLogin.setOnClickListener{ presenter.onLoginClicked()}}overridefunonDestroy(){super.onDestroy() presenter.detachView()}// 实现View接口overridefunshowLoading(){ binding.progressBar.visibility = View.VISIBLE binding.buttonLogin.isEnabled =false binding.buttonLogin.text ="登录中..."}overridefunhideLoading(){ binding.progressBar.visibility = View.GONE binding.buttonLogin.isEnabled =true binding.buttonLogin.text ="登录"}overridefunshowError(message: String){ Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()}overridefunonLoginSuccess(){startActivity(Intent(this, MainActivity::class.java))finish()}overridefungetUsername(): String = binding.editUsername.text.toString()overridefungetPassword(): String = binding.editPassword.text.toString()overridefunclearInputs(){ binding.editUsername.text.clear() binding.editPassword.text.clear()}}2.2 MVP的进阶优化
使用Kotlin特性优化MVP:
// 使用Kotlin委托简化Presenter管理abstractclass BaseMvpActivity<V : Any, P : BasePresenter<V>>:AppCompatActivity(){protectedlateinitvar presenter: P @Suppress("UNCHECKED_CAST")overridefunonCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState) presenter =createPresenter() presenter.attachView(thisas V)}overridefunonDestroy(){super.onDestroy() presenter.detachView()}abstractfuncreatePresenter(): P }// 使用泛型和反射简化Presenter创建abstractclass BasePresenter<V : Any>{protectedvar view: V?=nullfunattachView(view: V){this.view = view onViewAttached()}fundetachView(){onViewDetached()this.view =null}protectedopenfunonViewAttached(){}protectedopenfunonViewDetached(){}protectedfunwithView(block:(V)-> Unit){ view?.let(block)}}// 使用扩展函数简化UI操作fun LoginContract.View.showSuccessAndNavigate(message: String){showMessage(message)// 假设有showMessage方法onLoginSuccess()}// 使用密封类处理结果sealedclass LoginResult {dataclassSuccess(val user: User):LoginResult()dataclassError(val message: String):LoginResult()object Loading :LoginResult()object InvalidInput :LoginResult()}// 响应式PresenterclassReactiveLoginPresenter(privateval repository: UserRepository ): LoginContract.Presenter{privateval _state = MutableStateFlow<LoginResult>(LoginResult.InvalidInput)val state: StateFlow<LoginResult>= _state.asStateFlow()overridefunonLoginClicked(){// 通过状态流通知View更新}}2.3 MVP的优缺点分析
优点:
- View和Model完全解耦
- Presenter可测试(纯Java/Kotlin对象)
- 职责清晰,View只处理UI
缺点:
- 需要大量接口,代码量增加
- Presenter可能变得臃肿
- 生命周期管理复杂
Kotlin改进:
- 接口默认方法减少模板代码
- 协程简化异步和生命周期管理
- 扩展函数抽离通用逻辑
- 委托属性管理Presenter
三、MVVM模式:数据驱动的现代架构
3.1 MVVM概念解析
MVVM(Model-View-ViewModel) 是目前Android官方推荐的架构:
- ViewModel:管理UI相关数据, survives配置变化
- View:观察ViewModel的数据变化,自动更新
- Model:数据源和业务逻辑
核心:数据绑定(Data Binding)或视图绑定(View Binding)+ LiveData/StateFlow。
// Kotlin MVVM实现示例(使用Android Architecture Components)// ==================== Model层 ====================dataclassUser(val id: String,val name: String,val email: String,val token: String )sealedclass ApiResult<out T>{dataclass Success<T>(valdata: T): ApiResult<T>()dataclassError(val message: String): ApiResult<Nothing>()object Loading : ApiResult<Nothing>()}interface AuthRepository {suspendfunlogin(username: String, password: String): ApiResult<User>suspendfunregister(username: String, password: String, email: String): ApiResult<User>funlogout()}class AuthRepositoryImpl @Injectconstructor(privateval apiService: AuthApiService,privateval prefs: SharedPreferences ): AuthRepository {overridesuspendfunlogin(username: String, password: String): ApiResult<User>{returntry{// 实际网络请求val response = apiService.login(LoginRequest(username, password))if(response.isSuccessful){val user = response.body()?.toUser()?:throwException("数据解析错误")// 保存token prefs.edit().putString("auth_token", user.token).apply() ApiResult.Success(user)}else{ ApiResult.Error("登录失败: ${response.message()}")}}catch(e: Exception){ ApiResult.Error("网络错误: ${e.message}")}}// 其他方法...}// ==================== ViewModel层 ====================@HiltViewModelclass LoginViewModel @Injectconstructor(privateval repository: AuthRepository,privateval savedStateHandle: SavedStateHandle ):ViewModel(){// UI状态(使用密封类)privateval _uiState = MutableStateFlow<LoginUiState>(LoginUiState.Idle)val uiState: StateFlow<LoginUiState>= _uiState.asStateFlow()// 表单数据val username = savedStateHandle.getStateFlow("username","")val password = savedStateHandle.getStateFlow("password","")// 表单验证状态val isFormValid =combine(username, password){ user, pass -> user.isNotBlank()&& pass.isNotBlank()&& user.length >=3&& pass.length >=6}.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue =false)// 用户操作funonUsernameChanged(newValue: String){ savedStateHandle["username"]= newValue }funonPasswordChanged(newValue: String){ savedStateHandle["password"]= newValue }funonLoginClick(){ viewModelScope.launch{ _uiState.value = LoginUiState.Loading val result = repository.login(username.value, password.value) _uiState.value =when(result){is ApiResult.Success -> LoginUiState.Success(result.data)is ApiResult.Error -> LoginUiState.Error(result.message)else-> LoginUiState.Idle }}}funresetState(){ _uiState.value = LoginUiState.Idle }}// UI状态密封类sealedclass LoginUiState {object Idle :LoginUiState()object Loading :LoginUiState()dataclassSuccess(val user: User):LoginUiState()dataclassError(val message: String):LoginUiState()}// ==================== View层 ====================@AndroidEntryPointclass LoginFragment :Fragment(){privatevar _binding: FragmentLoginBinding?=nullprivateval binding get()= _binding!!privateval viewModel: LoginViewModel byviewModels()overridefunonCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentLoginBinding.inflate(inflater, container,false)return binding.root }overridefunonViewCreated(view: View, savedInstanceState: Bundle?){super.onViewCreated(view, savedInstanceState)setupViews()observeViewModel()}privatefunsetupViews(){// 使用数据绑定(也可用视图绑定) binding.apply{// 双向数据绑定 editUsername.doAfterTextChanged{ text -> viewModel.onUsernameChanged(text.toString())} editPassword.doAfterTextChanged{ text -> viewModel.onPasswordChanged(text.toString())} buttonLogin.setOnClickListener{ viewModel.onLoginClick()}// 根据表单状态更新按钮 viewLifecycleOwner.lifecycleScope.launch{ viewModel.isFormValid.collect{ isValid -> buttonLogin.isEnabled = isValid buttonLogin.alpha =if(isValid)1felse0.5f}}}}privatefunobserveViewModel(){ viewLifecycleOwner.lifecycleScope.launch{repeatOnLifecycle(Lifecycle.State.STARTED){ viewModel.uiState.collect{ state ->when(state){is LoginUiState.Idle ->handleIdleState()is LoginUiState.Loading ->handleLoadingState()is LoginUiState.Success ->handleSuccessState(state.user)is LoginUiState.Error ->handleErrorState(state.message)}}}}}privatefunhandleIdleState(){ binding.progressBar.hide() binding.buttonLogin.isEnabled =true}privatefunhandleLoadingState(){ binding.progressBar.show() binding.buttonLogin.isEnabled =false}privatefunhandleSuccessState(user: User){ binding.progressBar.hide()showSuccessMessage("欢迎回来,${user.name}!")// 导航到主界面findNavController().navigate( LoginFragmentDirections.actionLoginFragmentToHomeFragment(user))}privatefunhandleErrorState(message: String){ binding.progressBar.hide() binding.buttonLogin.isEnabled =trueshowErrorMessage(message)}privatefunshowSuccessMessage(message: String){ Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.success)).show()}privatefunshowErrorMessage(message: String){ Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.error)).setAction("重试"){ viewModel.onLoginClick()}.show()}overridefunonDestroyView(){super.onDestroyView() _binding =null}}3.2 MVVM的进阶:使用Jetpack Compose
// Jetpack Compose + MVVM + Kotlin的现代组合@ComposablefunLoginScreen( viewModel: LoginViewModel =hiltViewModel(), onLoginSuccess:(User)-> Unit ){val uiState by viewModel.uiState.collectAsStateWithLifecycle()val isFormValid by viewModel.isFormValid.collectAsStateWithLifecycle()Column( modifier = Modifier .fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ){// LogoIcon( painter =painterResource(R.drawable.ic_logo), contentDescription ="Logo", modifier = Modifier.size(100.dp), tint = MaterialTheme.colorScheme.primary )Spacer(modifier = Modifier.height(32.dp))// 用户名输入OutlinedTextField( value = viewModel.username.value, onValueChange = viewModel::onUsernameChanged, label ={Text("用户名")}, modifier = Modifier.fillMaxWidth(), isError = viewModel.username.value.isNotEmpty()&& viewModel.username.value.length <3)Spacer(modifier = Modifier.height(16.dp))// 密码输入OutlinedTextField( value = viewModel.password.value, onValueChange = viewModel::onPasswordChanged, label ={Text("密码")}, modifier = Modifier.fillMaxWidth(), visualTransformation =PasswordVisualTransformation(), isError = viewModel.password.value.isNotEmpty()&& viewModel.password.value.length <6)Spacer(modifier = Modifier.height(32.dp))// 登录按钮Button( onClick = viewModel::onLoginClick, modifier = Modifier.fillMaxWidth(), enabled = isFormValid && uiState !is LoginUiState.Loading ){if(uiState is LoginUiState.Loading){CircularProgressIndicator( modifier = Modifier.size(20.dp), color = MaterialTheme.colorScheme.onPrimary )Spacer(modifier = Modifier.width(8.dp))Text("登录中...")}else{Text("登录")}}// 处理状态when(val state = uiState){is LoginUiState.Error ->{Spacer(modifier = Modifier.height(16.dp))Text( text = state.message, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall )}is LoginUiState.Success ->{LaunchedEffect(state){onLoginSuccess(state.user)}}else->{}}}}3.3 MVVM的优缺点分析
优点:
- 数据驱动UI,自动更新
- ViewModel survives配置变化
- 配合Data Binding/Compose减少模板代码
- 官方支持,生态完善
缺点:
- 学习曲线较陡
- Data Binding调试困难
- 过度使用可能造成内存泄漏
Kotlin改进:
- 协程Flow替代LiveData(更强大)
- 密封类优雅处理状态
- 扩展函数简化数据绑定
- 委托属性管理ViewModel
四、架构模式对比与选型指南
4.1 三种架构模式对比
| 特性 | MVC | MVP | MVVM |
|---|---|---|---|
| 核心思想 | 分层 | 关注点分离 | 数据驱动 |
| View职责 | 展示+部分控制 | 仅展示 | 观察数据变化 |
| 测试难度 | 困难 | 中等 | 容易 |
| 代码量 | 少 | 多 | 中等 |
| 学习成本 | 低 | 中等 | 高 |
| 适合场景 | 小型项目 | 中型项目 | 中大型项目 |
| Kotlin适配 | 一般 | 良好 | 优秀 |
4.2 实际项目选型建议
// 项目架构决策器object ArchitectureDecider {fundecideArchitecture(project: ProjectInfo): Architecture {returnwhen{// 小型工具类应用 project.teamSize <=2&& project.complexity == Complexity.SIMPLE -> Architecture.MVC // 中型商业应用 project.teamSize in3..5&& project.hasUnitTest -> Architecture.MVP // 大型复杂应用 project.teamSize >5&& project.longTermMaintenance -> Architecture.MVVM // 使用Jetpack Compose project.usesCompose && project.minSdk >=21-> Architecture.MVVM_WITH_COMPOSE // 混合架构(过渡期)else-> Architecture.HYBRID }}}dataclassProjectInfo(val teamSize: Int,val complexity: Complexity,val hasUnitTest: Boolean,val longTermMaintenance: Boolean,val usesCompose: Boolean =false,val minSdk: Int =21)enumclass Complexity { SIMPLE, MEDIUM, COMPLEX }enumclass Architecture { MVC, MVP, MVVM, MVVM_WITH_COMPOSE, HYBRID }4.3 混合架构实践
在实际项目中,常常需要混合使用多种架构:
// 混合架构示例:MVVM + Clean Architecture@HiltViewModelclass UserProfileViewModel @Injectconstructor(privateval getUserUseCase: GetUserUseCase,privateval updateUserUseCase: UpdateUserUseCase, savedStateHandle: SavedStateHandle ):ViewModel(){// UI状态(MVVM)privateval _uiState = MutableStateFlow<UserProfileUiState>(UserProfileUiState.Loading)val uiState: StateFlow<UserProfileUiState>= _uiState.asStateFlow()// 从Clean Architecture的UseCase获取数据funloadUserProfile(userId: String){ viewModelScope.launch{ _uiState.value = UserProfileUiState.Loading val result = getUserUseCase.invoke(userId) _uiState.value =when(result){is Result.Success -> UserProfileUiState.Success(result.data)is Result.Error -> UserProfileUiState.Error(result.message)}}}// 使用MVP模式的契约接口进行模块间通信funupdateProfile(data: ProfileData){ viewModelScope.launch{val result = updateUserUseCase.invoke(data)if(result is Result.Success){// 通知其他模块(类似MVP的接口回调) eventChannel.send(ProfileUpdateEvent.Success)}}}privateval eventChannel = Channel<ProfileUpdateEvent>()val events = eventChannel.receiveAsFlow()}// Clean Architecture的UseCase(领域层)class GetUserUseCase @Injectconstructor(privateval userRepository: UserRepository ){suspendoperatorfuninvoke(userId: String): Result<User>{return userRepository.getUserById(userId)}}// 事件密封类(类似MVP的契约接口)sealedclass ProfileUpdateEvent {object Success :ProfileUpdateEvent()dataclassError(val message: String):ProfileUpdateEvent()}五、Kotlin在架构中的最佳实践
5.1 使用Kotlin特性优化架构代码
// 1. 使用密封类处理状态(替代枚举+接口)sealedclass Resource<out T>{dataclass Success<T>(valdata: T): Resource<T>()dataclassError(val exception: Throwable): Resource<Nothing>()object Loading : Resource<Nothing>()// 扩展函数处理不同状态funonSuccess(block:(T)-> Unit): Resource<T>=this.also{if(thisis Success)block(data)}funonError(block:(Throwable)-> Unit): Resource<T>=this.also{if(thisis Error)block(exception)}}// 2. 使用扩展函数简化架构组件fun<T> Flow<Resource<T>>.bindToView( lifecycleOwner: LifecycleOwner, onSuccess:(T)-> Unit, onError:(Throwable)-> Unit, onLoading:()-> Unit ={}){ lifecycleOwner.lifecycleScope.launch{ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED){[email protected]{ resource ->when(resource){is Resource.Loading ->onLoading()is Resource.Success ->onSuccess(resource.data)is Resource.Error ->onError(resource.exception)}}}}}// 3. 使用委托属性管理依赖interface Injectable {val injector: Injector get()= InjectorProvider.injector }class MainViewModel :ViewModel(), Injectable {// 延迟注入依赖privateval repository: UserRepository by inject {UserRepositoryImpl()}// 或者使用Koin/Koin/Hiltprivateval useCase: GetUserUseCase byinject()}// 4. 使用内联函数减少模板代码inlinefun<T> ViewModel.launchWithState( stateFlow: MutableStateFlow<Resource<T>>,crossinline block:suspend()-> T ){ viewModelScope.launch{ stateFlow.value = Resource.Loading try{val result =block() stateFlow.value = Resource.Success(result)}catch(e: Exception){ stateFlow.value = Resource.Error(e)}}}5.2 架构组件与Kotlin协程的完美结合
// 使用协程Flow构建响应式架构class ProductListViewModel @Injectconstructor(privateval getProductsUseCase: GetProductsUseCase,privateval savedStateHandle: SavedStateHandle ):ViewModel(){// 搜索词(状态保存)privateval searchQuery = savedStateHandle.getStateFlow("searchQuery","")// 排序方式privateval sortOrder =MutableStateFlow(SortOrder.DEFAULT)// 产品列表(自动响应搜索词和排序变化)val products: StateFlow<Resource<List<Product>>>=combine( searchQuery.debounce(300),// 防抖 sortOrder ){ query, order ->Pair(query, order)}.flatMapLatest{(query, order)->getProductsUseCase(query, order).map{ Resource.Success(it)}.catch{emit(Resource.Error(it))}.onStart{emit(Resource.Loading)}}.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = Resource.Loading )funonSearchQueryChanged(query: String){ savedStateHandle["searchQuery"]= query }funonSortOrderChanged(order: SortOrder){ sortOrder.value = order }}六、常见面试题
基础概念题
- MVC、MVP和MVVM三种架构模式的主要区别是什么?各有什么优缺点?
- 答案要点:MVC中Controller处理业务逻辑,View和Model可能耦合;MVP中Presenter处理所有逻辑,View被动;MVVM中ViewModel管理数据,View观察数据变化。MVC简单但易臃肿,MVP解耦但接口多,MVVM数据驱动但学习成本高。
- 在MVVM架构中,LiveData和StateFlow有什么区别?为什么推荐使用StateFlow?
- 答案要点:LiveData是生命周期感知的,但功能有限;StateFlow基于协程,支持复杂的变换操作、背压处理、线程切换。StateFlow更强大、更符合Kotlin协程生态、支持测试更方便。
- 如何防止在MVVM架构中出现内存泄漏?ViewModel的scope是如何管理的?
- 答案要点:1) 使用ViewLifecycleOwner而非Activity;2) 在onDestroyView中清除绑定;3) 使用repeatOnLifecycle收集Flow;4) ViewModel由ViewModelStore管理,在Activity重建时保持,在Finish时销毁。
实践应用题
- 将一个使用回调的MVC网络请求模块,改造成使用协程的MVVM架构,请写出关键代码。
- 在MVP架构中,如何处理Presenter的生命周期?如何避免内存泄漏?
- 答案要点:1) 在onCreate中创建Presenter,onDestroy中解绑;2) 使用弱引用持有View;3) 使用BasePresenter管理生命周期;4) 在View销毁时取消所有异步任务。示例:Presenter中定义attach/detach方法,Activity在对应生命周期调用。
- 如何设计一个支持离线功能的MVVM架构?请描述数据流向。
- 答案要点:采用Repository模式,数据流向:View → ViewModel → Repository → (优先缓存 → 网络)。关键点:1) 单一数据源;2) 缓存策略;3) 网络状态监测;4) 数据同步机制。使用Room缓存,Flow实现数据更新通知。
答案要点:
// 改造前(MVC+回调)class UserManager {funfetchUser(callback:(User)-> Unit){...}}// 改造后(MVVM+协程)class UserRepository {suspendfunfetchUser(): User =withContext(Dispatchers.IO){...}}class UserViewModel :ViewModel(){privateval _user = MutableStateFlow<Resource<User>>(Resource.Loading)val user: StateFlow<Resource<User>>= _user.asStateFlow()funloadUser(){ viewModelScope.launch{ _user.value = Resource.Loading try{val user = repository.fetchUser() _user.value = Resource.Success(user)}catch(e: Exception){ _user.value = Resource.Error(e)}}}}深入原理题
- Data Binding和View Binding有什么区别?在MVVM中如何选择?
- 答案要点:DataBinding支持双向绑定和表达式,但编译速度慢、调试困难;ViewBinding只生成视图引用,简单高效。选择:简单场景用ViewBinding,复杂数据绑定用DataBinding,或两者混合使用(主要用ViewBinding,局部用DataBinding)。
- 在大型项目中,如何组织MVVM的模块结构?如何避免ViewModel臃肿?
- 答案要点:按功能模块划分:feature包(View+ViewModel)、data包(Repository+Model)、domain包(UseCase)。避免ViewModel臃肿:1) 使用UseCase封装业务逻辑;2) 使用State管理UI状态;3) 使用Event处理一次性事件;4) 提取基类ViewModel。
- 如何测试MVVM架构中的ViewModel?如何模拟依赖?
- 答案要点:使用JUnit + MockK + Turbine。步骤:1) 使用依赖注入(Hilt/Koin);2) 测试中提供模拟依赖;3) 测试StateFlow的状态变化;4) 测试协程执行逻辑。示例:使用TestCoroutineDispatcher,验证状态流转。
总结
从MVC到MVVM,不仅是架构模式的演进,更是开发思维从"界面驱动"到"数据驱动"的转变。Kotlin作为现代编程语言,其协程、扩展函数、密封类、Flow等特性,与MVVM架构完美契合,让Android开发变得更加高效、优雅。
对于从Java转向Kotlin的开发者,建议的学习路径是:
- 先理解MVC:掌握基础分层思想
- 实践MVP:体会接口契约和测试优势
- 深入MVVM:掌握数据驱动和响应式编程
- 融合最佳实践:结合Clean Architecture、UseCase等
在实际项目中,没有"最好"的架构,只有"最适合"的架构。小型工具类应用可能MVC就够了,中型商业应用适合MVP,大型复杂应用则推荐MVVM。重要的是保持架构一致性和可维护性。
随着Jetpack Compose的成熟,"Compose + MVVM"正在成为新的标准范式。掌握Kotlin和现代Android架构,就能在移动开发的道路上走得更远。