架构演进:从混沌到清晰

Android应用架构经历了从Activity/Fragment直接操作一切,到MVC、MVP、MVVM,再到现代MVI和Clean Architecture的演进。每一次演进都是为了解决特定的问题:代码耦合、测试困难、状态管理混乱。

架构演进的核心驱动力

  • 关注点分离:UI、业务逻辑、数据访问层职责清晰
  • 可测试性:业务逻辑独立于Android框架,便于单元测试
  • 可维护性:代码结构清晰,新人上手快
  • 生命周期感知:正确处理配置变更和生命周期事件

MVP:分离视图与逻辑

MVP架构设计

MVP(Model-View-Presenter)将业务逻辑从Activity/Fragment中抽离,Presenter作为中间层协调View和Model:

// Contract:定义View和Presenter接口
interface LoginContract {
    interface View {
        fun showLoading()
        fun hideLoading()
        fun showError(message: String)
        fun navigateToHome()
    }
    
    interface Presenter {
        fun attachView(view: View)
        fun detachView()
        fun login(username: String, password: String)
    }
}

// Presenter实现
class LoginPresenter(
    private val userRepository: UserRepository
) : LoginContract.Presenter {
    
    private var view: LoginContract.View? = null
    private val scope = CoroutineScope(Dispatchers.Main + Job())
    
    override fun attachView(view: LoginContract.View) {
        this.view = view
    }
    
    override fun detachView() {
        view = null
        scope.cancel() // 防止内存泄漏
    }
    
    override fun login(username: String, password: String) {
        view?.showLoading()
        
        scope.launch {
            try {
                val result = withContext(Dispatchers.IO) {
                    userRepository.login(username, password)
                }
                
                when (result) {
                    is Result.Success -> view?.navigateToHome()
                    is Result.Error -> view?.showError(result.message)
                }
            } finally {
                view?.hideLoading()
            }
        }
    }
}

// Activity实现View接口
class LoginActivity : AppCompatActivity(), LoginContract.View {
    
    private lateinit var presenter: LoginContract.Presenter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        
        // 依赖注入(实际项目中使用Dagger/Hilt)
        presenter = LoginPresenter(UserRepositoryImpl())
        presenter.attachView(this)
        
        loginButton.setOnClickListener {
            presenter.login(usernameEdit.text.toString(), 
                          passwordEdit.text.toString())
        }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        presenter.detachView() // 关键:防止内存泄漏
    }
    
    override fun showLoading() { progressBar.visibility = View.VISIBLE }
    override fun hideLoading() { progressBar.visibility = View.GONE }
    override fun showError(message: String) { 
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 
    }
    override fun navigateToHome() { 
        startActivity(Intent(this, HomeActivity::class.java))
        finish()
    }
}

MVP设计要点

  • Presenter持有View的弱引用或手动管理生命周期
  • View接口只定义UI操作,不包含业务逻辑
  • Presenter可单元测试,不依赖Android框架

MVVM:响应式数据绑定

Jetpack ViewModel + LiveData

MVVM通过数据绑定实现View和ViewModel的自动同步,结合Jetpack组件实现生命周期感知:

// ViewModel:业务逻辑持有者
class LoginViewModel(
    private val userRepository: UserRepository
) : ViewModel() {
    
    // LiveData:可观察的数据持有者
    private val _loginState = MutableLiveData()
    val loginState: LiveData = _loginState
    
    private val _isLoading = MutableLiveData()
    val isLoading: LiveData = _isLoading
    
    fun login(username: String, password: String) {
        if (!validateInput(username, password)) {
            _loginState.value = LoginState.Error("Invalid input")
            return
        }
        
        _isLoading.value = true
        
        viewModelScope.launch {
            try {
                val result = userRepository.login(username, password)
                _loginState.value = when (result) {
                    is Result.Success -> LoginState.Success
                    is Result.Error -> LoginState.Error(result.message)
                }
            } finally {
                _isLoading.value = false
            }
        }
    }
    
    private fun validateInput(username: String, password: String): Boolean {
        return username.isNotBlank() && password.length >= 6
    }
    
    // 密封类表示状态
    sealed class LoginState {
        object Success : LoginState()
        data class Error(val message: String) : LoginState()
    }
}

// Activity:观察数据变化,自动更新UI
class LoginActivity : AppCompatActivity() {
    
    private val viewModel: LoginViewModel by viewModels {
        LoginViewModelFactory(UserRepositoryImpl())
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        
        // 观察Loading状态
        viewModel.isLoading.observe(this) { isLoading ->
            progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
            loginButton.isEnabled = !isLoading
        }
        
        // 观察登录状态
        viewModel.loginState.observe(this) { state ->
            when (state) {
                is LoginViewModel.LoginState.Success -> navigateToHome()
                is LoginViewModel.LoginState.Error -> showError(state.message)
            }
        }
        
        loginButton.setOnClickListener {
            viewModel.login(
                usernameEdit.text.toString(),
                passwordEdit.text.toString()
            )
        }
    }
}

StateFlow替代LiveData

Kotlin协程的StateFlow提供了更灵活的响应式编程方式:

class ModernLoginViewModel(
    private val userRepository: UserRepository
) : ViewModel() {
    
    // StateFlow:LiveData的替代方案,支持多次观察
    private val _uiState = MutableStateFlow(LoginUiState())
    val uiState: StateFlow = _uiState.asStateFlow()
    
    fun login(username: String, password: String) {
        _uiState.update { it.copy(isLoading = true) }
        
        viewModelScope.launch {
            userRepository.login(username, password)
                .onSuccess { user ->
                    _uiState.update { 
                        it.copy(isLoading = false, user = user, error = null) 
                    }
                }
                .onFailure { exception ->
                    _uiState.update { 
                        it.copy(isLoading = false, error = exception.message) 
                    }
                }
        }
    }
    
    data class LoginUiState(
        val isLoading: Boolean = false,
        val user: User? = null,
        val error: String? = null
    )
}

// Activity中使用
class ModernLoginActivity : AppCompatActivity() {
    
    private val viewModel: ModernLoginViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 收集StateFlow(lifecycle-aware)
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    updateUi(state)
                }
            }
        }
    }
    
    private fun updateUi(state: ModernLoginViewModel.LoginUiState) {
        progressBar.isVisible = state.isLoading
        state.error?.let { showError(it) }
        state.user?.let { navigateToHome() }
    }
}

MVI:单向数据流

MVI架构设计

MVI(Model-View-Intent)强调单向数据流和不可变状态,是MVVM的进一步演进:

// MVI核心组件

// 1. State:不可变的UI状态
data class NewsUiState(
    val isLoading: Boolean = false,
    val news: List = emptyList(),
    val error: String? = null,
    val selectedCategory: String = "all"
)

// 2. Intent:用户意图/事件
sealed class NewsIntent {
    object LoadNews : NewsIntent()
    data class SelectCategory(val category: String) : NewsIntent()
    data class RefreshNews(val forceRefresh: Boolean) : NewsIntent()
    data class NewsClicked(val newsId: String) : NewsIntent()
}

// 3. Effect:一次性事件(如导航、Toast)
sealed class NewsEffect {
    data class NavigateToDetail(val newsId: String) : NewsEffect()
    data class ShowToast(val message: String) : NewsEffect()
}

// 4. ViewModel:处理Intent,产生新State
class NewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {
    
    private val _state = MutableStateFlow(NewsUiState())
    val state: StateFlow = _state.asStateFlow()
    
    private val _effect = Channel()
    val effect: Flow = _effect.receiveAsFlow()
    
    fun processIntent(intent: NewsIntent) {
        when (intent) {
            is NewsIntent.LoadNews -> loadNews()
            is NewsIntent.SelectCategory -> selectCategory(intent.category)
            is NewsIntent.RefreshNews -> refreshNews(intent.forceRefresh)
            is NewsIntent.NewsClicked -> handleNewsClick(intent.newsId)
        }
    }
    
    private fun loadNews() {
        _state.update { it.copy(isLoading = true) }
        
        viewModelScope.launch {
            newsRepository.getNews(_state.value.selectedCategory)
                .onSuccess { news ->
                    _state.update { 
                        it.copy(isLoading = false, news = news, error = null) 
                    }
                }
                .onFailure { error ->
                    _state.update { 
                        it.copy(isLoading = false, error = error.message) 
                    }
                }
        }
    }
    
    private fun handleNewsClick(newsId: String) {
        viewModelScope.launch {
            _effect.send(NewsEffect.NavigateToDetail(newsId))
        }
    }
    
    // ... 其他处理方法
}

// Activity:发送Intent,观察State和Effect
class NewsActivity : AppCompatActivity() {
    
    private val viewModel: NewsViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_news)
        
        // 观察State
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.state.collect { state ->
                    render(state)
                }
            }
        }
        
        // 观察Effect
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.effect.collect { effect ->
                    handleEffect(effect)
                }
            }
        }
        
        // 发送Intent
        swipeRefresh.setOnRefreshListener {
            viewModel.processIntent(NewsIntent.RefreshNews(forceRefresh = true))
        }
        
        categoryChipGroup.setOnCheckedChangeListener { _, checkedId ->
            val category = when (checkedId) {
                R.id.chipTech -> "tech"
                R.id.chipSports -> "sports"
                else -> "all"
            }
            viewModel.processIntent(NewsIntent.SelectCategory(category))
        }
        
        // 初始加载
        viewModel.processIntent(NewsIntent.LoadNews)
    }
    
    private fun render(state: NewsUiState) {
        swipeRefresh.isRefreshing = state.isLoading
        newsAdapter.submitList(state.news)
        state.error?.let { showError(it) }
    }
    
    private fun handleEffect(effect: NewsEffect) {
        when (effect) {
            is NewsEffect.NavigateToDetail -> {
                startActivity(NewsDetailActivity.newIntent(this, effect.newsId))
            }
            is NewsEffect.ShowToast -> {
                Toast.makeText(this, effect.message, Toast.LENGTH_SHORT).show()
            }
        }
    }
}

MVI的优势

  • 可预测性:单一数据源,状态变化可追踪
  • 可调试性:每个状态变化都有对应的Intent
  • 时间旅行调试:可以记录和回放状态变化
  • 测试友好:Intent -> State的纯函数易于测试

Clean Architecture:分层架构

分层设计原则

// Clean Architecture分层
// 1. Domain层(最内层,不依赖任何框架)
// 2. Data层(数据访问,依赖Domain)
// 3. Presentation层(UI,依赖Domain)

// Domain层:实体和用例
// domain/model/User.kt
data class User(
    val id: String,
    val name: String,
    val email: String
)

// domain/repository/UserRepository.kt
interface UserRepository {
    suspend fun getUser(id: String): Result
    suspend fun saveUser(user: User): Result
}

// domain/usecase/GetUserUseCase.kt
class GetUserUseCase(private val userRepository: UserRepository) {
    suspend operator fun invoke(userId: String): Result {
        return userRepository.getUser(userId)
    }
}

// Data层:Repository实现
// data/repository/UserRepositoryImpl.kt
class UserRepositoryImpl(
    private val userDao: UserDao,
    private val userApi: UserApi
) : UserRepository {
    
    override suspend fun getUser(id: String): Result {
        return try {
            // 优先从本地获取
            val localUser = userDao.getUser(id)
            if (localUser != null) {
                return Result.success(localUser.toDomainModel())
            }
            
            // 从网络获取并缓存
            val remoteUser = userApi.getUser(id)
            userDao.insertUser(remoteUser.toEntity())
            Result.success(remoteUser.toDomainModel())
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    override suspend fun saveUser(user: User): Result {
        // 实现保存逻辑
        return Result.success(Unit)
    }
}

// Presentation层:ViewModel
// presentation/viewmodel/UserViewModel.kt
class UserViewModel(
    private val getUserUseCase: GetUserUseCase
) : ViewModel() {
    
    private val _user = MutableStateFlow(null)
    val user: StateFlow = _user.asStateFlow()
    
    fun loadUser(userId: String) {
        viewModelScope.launch {
            getUserUseCase(userId)
                .onSuccess { _user.value = it }
                .onFailure { /* 处理错误 */ }
        }
    }
}

依赖规则

  • 内层不依赖外层
  • 依赖方向:Presentation -> Domain <- Data
  • 通过接口实现依赖倒置
  • 便于单元测试和模块替换

架构选型决策树

架构 适用场景 优点 缺点
MVP 中小型项目,需要清晰分离 职责清晰,可测试 接口膨胀,样板代码多
MVVM 大多数现代Android项目 生命周期感知,数据驱动 数据绑定调试困难
MVI 复杂状态管理,团队协作 单向数据流,可预测 学习曲线陡峭
Clean Architecture 大型项目,长期维护 高度解耦,可测试 过度设计风险

架构设计原则

  • 不要过度设计:简单项目用简单架构
  • 保持一致性:团队统一架构规范
  • 可测试性优先:架构应便于单元测试
  • 渐进式演进:随项目发展调整架构

总结

Android架构演进的核心是从混乱到有序,从紧耦合到高内聚低耦合。没有最好的架构,只有最适合的架构。理解每种架构的设计思想和适用场景,根据项目规模、团队能力和业务复杂度做出合理选择,是Android架构师的核心能力。