从Java到Kotlin,不仅仅是语法的升级,更是架构思维的进化。本文将带你全面掌握Kotlin在Android开发中三种核心架构模式:MVC、MVP、MVVM,揭示如何用Kotlin特性写出更优雅的架构代码。
引言:为什么Android开发者需要架构模式?
如果你有过Java Android开发经验,一定遇到过这些问题:Activity/Fragment代码臃肿(动辄上千行)、业务逻辑与UI逻辑混杂、测试困难、代码难以维护。架构模式正是为了解决这些问题而生的。而Kotlin,以其简洁的语法和强大的特性,让这些架构模式的实现变得更加优雅。
让我们从一个真实案例开始------用户登录模块,看看在不同架构模式下的演变:
kotlin
// 糟糕的无架构代码(Java风格)
class BadLoginActivity : AppCompatActivity() {
private lateinit var editUsername: EditText
private lateinit var editPassword: EditText
private lateinit var buttonLogin: Button
override fun onCreate(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()
}
}
private fun validateCredentials(username: String, password: String): Boolean {
return username.length >= 3 && password.length >= 6
}
private fun showLoading() { /* 显示加载动画 */ }
private fun hideLoading() { /* 隐藏加载动画 */ }
}
这样的代码有太多问题:职责不清、难以测试、回调地狱。接下来,让我们看看如何用架构模式重构它。
一、MVC模式:最基础的分层架构
1.1 MVC概念解析
MVC(Model-View-Controller) 是最经典的架构模式,将应用分为三层:
- Model(模型):数据和业务逻辑
- View(视图):UI展示
- Controller(控制器):处理用户输入,协调Model和View
在Android中,通常:
- View = XML布局 + Activity/Fragment(视图部分)
- Controller = Activity/Fragment(控制逻辑部分)
- Model = 数据类、仓库、业务逻辑类
kotlin
// Kotlin MVC实现示例
// ==================== Model层 ====================
data class User(val username: String, val password: String)
class LoginModel {
// 业务逻辑
fun validateCredentials(username: String, password: String): Boolean {
return username.length >= 3 && password.length >= 6
}
// 模拟网络请求
suspend fun login(username: String, password: String): Boolean {
delay(2000) // 模拟网络延迟
return username == "admin" && password == "123456"
}
}
// ==================== View层 ====================
// activity_login.xml (UI布局)
// ==================== Controller层 ====================
class LoginController(private val view: LoginView) {
private val model = LoginModel()
fun onLoginClicked(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 {
fun showLoading()
fun hideLoading()
fun showError(message: String)
fun onLoginSuccess()
fun launchOnLifecycle(block: suspend () -> Unit)
}
// ==================== Activity(View+Controller混合)====================
class LoginActivity : AppCompatActivity(), LoginView {
private lateinit var controller: LoginController
private lateinit var binding: ActivityLoginBinding
override fun onCreate(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)
}
}
override fun showLoading() {
binding.progressBar.visibility = View.VISIBLE
binding.buttonLogin.isEnabled = false
}
override fun hideLoading() {
binding.progressBar.visibility = View.GONE
binding.buttonLogin.isEnabled = true
}
override fun showError(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
override fun onLoginSuccess() {
Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, MainActivity::class.java))
finish()
}
override fun launchOnLifecycle(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
// Kotlin MVP实现示例
// ==================== Contract(契约接口)====================
// 使用Kotlin的接口默认方法
interface LoginContract {
interface View {
fun showLoading()
fun hideLoading()
fun showError(message: String)
fun onLoginSuccess()
fun getUsername(): String
fun getPassword(): String
fun clearInputs()
}
interface Presenter {
fun attachView(view: View)
fun detachView()
fun onLoginClicked()
fun validateInput(): Boolean
}
}
// ==================== Model层 ====================
data class User(val id: String, val name: String, val token: String)
interface UserRepository {
suspend fun login(username: String, password: String): Result<User>
}
class UserRepositoryImpl : UserRepository {
override suspend fun login(username: String, password: String): Result<User> {
delay(2000) // 模拟网络延迟
return if (username == "admin" && password == "123456") {
Result.success(User("001", "管理员", "token_abc123"))
} else {
Result.failure(Exception("用户名或密码错误"))
}
}
}
// ==================== Presenter层 ====================
class LoginPresenter(
private val repository: UserRepository,
private val dispatcher: CoroutineDispatcher = Dispatchers.Main
) : LoginContract.Presenter {
private var view: LoginContract.View? = null
private var job: Job? = null
override fun attachView(view: LoginContract.View) {
this.view = view
}
override fun detachView() {
job?.cancel()
view = null
}
override fun onLoginClicked() {
// 验证输入
if (!validateInput()) {
return
}
val view = this.view ?: return
val 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()
}
}
}
override fun validateInput(): Boolean {
val view = this.view ?: return false
val username = view.getUsername()
val password = view.getPassword()
return when {
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
}
}
private fun handleLoginResult(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 {
private lateinit var presenter: LoginPresenter
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
// 初始化Presenter
val repository = UserRepositoryImpl()
presenter = LoginPresenter(repository)
presenter.attachView(this)
binding.buttonLogin.setOnClickListener {
presenter.onLoginClicked()
}
}
override fun onDestroy() {
super.onDestroy()
presenter.detachView()
}
// 实现View接口
override fun showLoading() {
binding.progressBar.visibility = View.VISIBLE
binding.buttonLogin.isEnabled = false
binding.buttonLogin.text = "登录中..."
}
override fun hideLoading() {
binding.progressBar.visibility = View.GONE
binding.buttonLogin.isEnabled = true
binding.buttonLogin.text = "登录"
}
override fun showError(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
}
override fun onLoginSuccess() {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
override fun getUsername(): String = binding.editUsername.text.toString()
override fun getPassword(): String = binding.editPassword.text.toString()
override fun clearInputs() {
binding.editUsername.text.clear()
binding.editPassword.text.clear()
}
}
2.2 MVP的进阶优化
使用Kotlin特性优化MVP:
kotlin
// 使用Kotlin委托简化Presenter管理
abstract class BaseMvpActivity<V : Any, P : BasePresenter<V>> : AppCompatActivity() {
protected lateinit var presenter: P
@Suppress("UNCHECKED_CAST")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = createPresenter()
presenter.attachView(this as V)
}
override fun onDestroy() {
super.onDestroy()
presenter.detachView()
}
abstract fun createPresenter(): P
}
// 使用泛型和反射简化Presenter创建
abstract class BasePresenter<V : Any> {
protected var view: V? = null
fun attachView(view: V) {
this.view = view
onViewAttached()
}
fun detachView() {
onViewDetached()
this.view = null
}
protected open fun onViewAttached() {}
protected open fun onViewDetached() {}
protected fun withView(block: (V) -> Unit) {
view?.let(block)
}
}
// 使用扩展函数简化UI操作
fun LoginContract.View.showSuccessAndNavigate(message: String) {
showMessage(message) // 假设有showMessage方法
onLoginSuccess()
}
// 使用密封类处理结果
sealed class LoginResult {
data class Success(val user: User) : LoginResult()
data class Error(val message: String) : LoginResult()
object Loading : LoginResult()
object InvalidInput : LoginResult()
}
// 响应式Presenter
class ReactiveLoginPresenter(
private val repository: UserRepository
) : LoginContract.Presenter {
private val _state = MutableStateFlow<LoginResult>(LoginResult.InvalidInput)
val state: StateFlow<LoginResult> = _state.asStateFlow()
override fun onLoginClicked() {
// 通过状态流通知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
// Kotlin MVVM实现示例(使用Android Architecture Components)
// ==================== Model层 ====================
data class User(
val id: String,
val name: String,
val email: String,
val token: String
)
sealed class ApiResult<out T> {
data class Success<T>(val data: T) : ApiResult<T>()
data class Error(val message: String) : ApiResult<Nothing>()
object Loading : ApiResult<Nothing>()
}
interface AuthRepository {
suspend fun login(username: String, password: String): ApiResult<User>
suspend fun register(username: String, password: String, email: String): ApiResult<User>
fun logout()
}
class AuthRepositoryImpl @Inject constructor(
private val apiService: AuthApiService,
private val prefs: SharedPreferences
) : AuthRepository {
override suspend fun login(username: String, password: String): ApiResult<User> {
return try {
// 实际网络请求
val response = apiService.login(LoginRequest(username, password))
if (response.isSuccessful) {
val user = response.body()?.toUser() ?: throw Exception("数据解析错误")
// 保存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层 ====================
@HiltViewModel
class LoginViewModel @Inject constructor(
private val repository: AuthRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// UI状态(使用密封类)
private val _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
)
// 用户操作
fun onUsernameChanged(newValue: String) {
savedStateHandle["username"] = newValue
}
fun onPasswordChanged(newValue: String) {
savedStateHandle["password"] = newValue
}
fun onLoginClick() {
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
}
}
}
fun resetState() {
_uiState.value = LoginUiState.Idle
}
}
// UI状态密封类
sealed class LoginUiState {
object Idle : LoginUiState()
object Loading : LoginUiState()
data class Success(val user: User) : LoginUiState()
data class Error(val message: String) : LoginUiState()
}
// ==================== View层 ====================
@AndroidEntryPoint
class LoginFragment : Fragment() {
private var _binding: FragmentLoginBinding? = null
private val binding get() = _binding!!
private val viewModel: LoginViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLoginBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupViews()
observeViewModel()
}
private fun setupViews() {
// 使用数据绑定(也可用视图绑定)
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) 1f else 0.5f
}
}
}
}
private fun observeViewModel() {
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)
}
}
}
}
}
private fun handleIdleState() {
binding.progressBar.hide()
binding.buttonLogin.isEnabled = true
}
private fun handleLoadingState() {
binding.progressBar.show()
binding.buttonLogin.isEnabled = false
}
private fun handleSuccessState(user: User) {
binding.progressBar.hide()
showSuccessMessage("欢迎回来,${user.name}!")
// 导航到主界面
findNavController().navigate(
LoginFragmentDirections.actionLoginFragmentToHomeFragment(user)
)
}
private fun handleErrorState(message: String) {
binding.progressBar.hide()
binding.buttonLogin.isEnabled = true
showErrorMessage(message)
}
private fun showSuccessMessage(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG)
.setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.success))
.show()
}
private fun showErrorMessage(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG)
.setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.error))
.setAction("重试") {
viewModel.onLoginClick()
}
.show()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
3.2 MVVM的进阶:使用Jetpack Compose
kotlin
// Jetpack Compose + MVVM + Kotlin的现代组合
@Composable
fun LoginScreen(
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
) {
// Logo
Icon(
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 实际项目选型建议
kotlin
// 项目架构决策器
object ArchitectureDecider {
fun decideArchitecture(project: ProjectInfo): Architecture {
return when {
// 小型工具类应用
project.teamSize <= 2 && project.complexity == Complexity.SIMPLE
-> Architecture.MVC
// 中型商业应用
project.teamSize in 3..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
}
}
}
data class ProjectInfo(
val teamSize: Int,
val complexity: Complexity,
val hasUnitTest: Boolean,
val longTermMaintenance: Boolean,
val usesCompose: Boolean = false,
val minSdk: Int = 21
)
enum class Complexity { SIMPLE, MEDIUM, COMPLEX }
enum class Architecture { MVC, MVP, MVVM, MVVM_WITH_COMPOSE, HYBRID }
4.3 混合架构实践
在实际项目中,常常需要混合使用多种架构:
kotlin
// 混合架构示例:MVVM + Clean Architecture
@HiltViewModel
class UserProfileViewModel @Inject constructor(
private val getUserUseCase: GetUserUseCase,
private val updateUserUseCase: UpdateUserUseCase,
savedStateHandle: SavedStateHandle
) : ViewModel() {
// UI状态(MVVM)
private val _uiState = MutableStateFlow<UserProfileUiState>(UserProfileUiState.Loading)
val uiState: StateFlow<UserProfileUiState> = _uiState.asStateFlow()
// 从Clean Architecture的UseCase获取数据
fun loadUserProfile(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模式的契约接口进行模块间通信
fun updateProfile(data: ProfileData) {
viewModelScope.launch {
val result = updateUserUseCase.invoke(data)
if (result is Result.Success) {
// 通知其他模块(类似MVP的接口回调)
eventChannel.send(ProfileUpdateEvent.Success)
}
}
}
private val eventChannel = Channel<ProfileUpdateEvent>()
val events = eventChannel.receiveAsFlow()
}
// Clean Architecture的UseCase(领域层)
class GetUserUseCase @Inject constructor(
private val userRepository: UserRepository
) {
suspend operator fun invoke(userId: String): Result<User> {
return userRepository.getUserById(userId)
}
}
// 事件密封类(类似MVP的契约接口)
sealed class ProfileUpdateEvent {
object Success : ProfileUpdateEvent()
data class Error(val message: String) : ProfileUpdateEvent()
}
五、Kotlin在架构中的最佳实践
5.1 使用Kotlin特性优化架构代码
kotlin
// 1. 使用密封类处理状态(替代枚举+接口)
sealed class Resource<out T> {
data class Success<T>(val data: T) : Resource<T>()
data class Error(val exception: Throwable) : Resource<Nothing>()
object Loading : Resource<Nothing>()
// 扩展函数处理不同状态
fun onSuccess(block: (T) -> Unit): Resource<T> = this.also {
if (this is Success) block(data)
}
fun onError(block: (Throwable) -> Unit): Resource<T> = this.also {
if (this is 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) {
this@bindToView.collect { 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 {
// 延迟注入依赖
private val repository: UserRepository by inject { UserRepositoryImpl() }
// 或者使用Koin/Koin/Hilt
private val useCase: GetUserUseCase by inject()
}
// 4. 使用内联函数减少模板代码
inline fun <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协程的完美结合
kotlin
// 使用协程Flow构建响应式架构
class ProductListViewModel @Inject constructor(
private val getProductsUseCase: GetProductsUseCase,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 搜索词(状态保存)
private val searchQuery = savedStateHandle.getStateFlow("searchQuery", "")
// 排序方式
private val 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
)
fun onSearchQueryChanged(query: String) {
savedStateHandle["searchQuery"] = query
}
fun onSortOrderChanged(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架构,请写出关键代码。
-
答案要点 :
kotlin// 改造前(MVC+回调) class UserManager { fun fetchUser(callback: (User) -> Unit) { ... } } // 改造后(MVVM+协程) class UserRepository { suspend fun fetchUser(): User = withContext(Dispatchers.IO) { ... } } class UserViewModel : ViewModel() { private val _user = MutableStateFlow<Resource<User>>(Resource.Loading) val user: StateFlow<Resource<User>> = _user.asStateFlow() fun loadUser() { viewModelScope.launch { _user.value = Resource.Loading try { val user = repository.fetchUser() _user.value = Resource.Success(user) } catch (e: Exception) { _user.value = Resource.Error(e) } } } }
-
-
在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实现数据更新通知。
深入原理题
-
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架构,就能在移动开发的道路上走得更远。