关于Android Compose架构的思考

Compose 本身是声明式 UI 框架 ,天然适配「响应式架构」,传统的 MVC/MVP 因「命令式思维」(手动更新 UI)已不再适配,主流架构是 MVVM(最贴合) ,进阶场景会结合「单向数据流(UDF)」「Clean Architecture(整洁架构)」设计,核心目标是让数据状态单向流转,避免 UI 与业务逻辑耦合。

一、先明确:Compose 为什么"抛弃"MVC/MVP?

架构 核心问题(适配 Compose 时)
MVC Controller 直接操作 UI(Compose 中 UI 由状态驱动,无" findViewById + 更新"的操作入口),易导致状态混乱
MVP Presenter 持有 View 接口,通过接口回调更新 UI(Compose 中无"View 接口",回调会破坏声明式思想,增加模板代码)
MVVM ViewModel 持有可观察状态,UI 仅订阅状态(完全贴合 Compose "状态驱动 UI"的核心,无手动更新逻辑)

二、Compose 主流架构:MVVM + 单向数据流(UDF)

这是 Google 官方推荐的架构,核心是「状态上移、单向流转」,分为 4 层,职责清晰且适配 Compose 的重组特性:

1. 架构分层(从上到下)

swift 复制代码
┌─────────────────────────┐
│ UI 层(Composable 函数) │ 仅订阅状态、触发事件,无业务逻辑
└─────────────┬───────────┘
              │ 发送事件(如点击)/ 接收状态更新
┌─────────────▼───────────┐
│ ViewModel 层(状态持有) │ 处理UI事件、管理状态、调用仓库层,不持有UI引用
└─────────────┬───────────┘
              │ 调用接口/本地数据
┌─────────────▼───────────┐
│ Repository 层(数据仓库)│ 统一管理数据来源(网络/本地DB/SharedPref),解耦数据逻辑
└─────────────┬───────────┘
              │ 数据请求/存储
┌─────────────▼───────────┐
│ Data 层(数据源)│ 网络接口(Retrofit)、本地DB(Room)、SP等
└─────────────────────────┘

2. 核心原则:单向数据流(UDF)

数据只能沿「Data → Repository → ViewModel → UI」方向流转,反向仅通过「事件」触发,避免状态混乱:

复制代码
UI 触发事件(如点击按钮)→ ViewModel 处理事件 → 调用 Repository 获取数据 → ViewModel 更新状态 → UI 自动重组 

三、完整示例:MVVM + 单向数据流

以"登录页"为例,覆盖状态管理、事件处理、数据请求全流程:

Step 1:Data 层(网络请求)

kotlin 复制代码
// 1. 数据模型
data class LoginRequest(val username: String, val password: String)
data class LoginResponse(val token: String, val userId: String)

// 2. 网络接口(Retrofit)
interface LoginApi {
    @POST("login")
    suspend fun login(@Body request: LoginRequest): LoginResponse
}

// 3. 本地数据源(示例:SharedPref 保存 Token)
class LoginLocalDataSource(context: Context) {
    private val sp = context.getSharedPreferences("login", Context.MODE_PRIVATE)
    
    fun saveToken(token: String) {
        sp.edit().putString("token", token).apply()
    }
    
    fun getToken(): String? = sp.getString("token", null)
}

Step 2:Repository 层(数据仓库)

统一管理数据来源,ViewModel 无需关心数据从网络/本地来:

kotlin 复制代码
class LoginRepository(
    private val api: LoginApi,
    private val localDataSource: LoginLocalDataSource
) {
    // 登录请求(挂起函数,适配协程)
    suspend fun login(username: String, password: String): LoginResponse {
        val response = api.login(LoginRequest(username, password))
        // 登录成功后保存 Token 到本地
        localDataSource.saveToken(response.token)
        return response
    }
}

Step 3:ViewModel 层(状态持有 + 事件处理)

核心是用 StateFlow/LiveData 持有可观察状态,Compose 订阅后自动重组:

kotlin 复制代码
class LoginViewModel(
    private val repository: LoginRepository
) : ViewModel() {
    // 1. UI 状态(聚合所有需要驱动 UI 的数据)
    private val _uiState = MutableStateFlow(LoginUiState())
    val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow() // 对外暴露只读流

    // 2. 处理登录事件(UI 点击后调用)
    fun handleLogin(username: String, password: String) {
        // 标记加载中
        _uiState.update { it.copy(isLoading = true, errorMsg = "") }
        
        // 协程处理异步请求(viewModelScope 自动绑定生命周期)
        viewModelScope.launch {
            try {
                val response = repository.login(username, password)
                // 登录成功:更新状态
                _uiState.update { 
                    it.copy(
                        isLoading = false,
                        isLoginSuccess = true,
                        token = response.token
                    )
                }
            } catch (e: Exception) {
                // 登录失败:更新错误状态
                _uiState.update { 
                    it.copy(
                        isLoading = false,
                        errorMsg = e.message ?: "登录失败,请重试"
                    )
                }
            }
        }
    }

    // 定义 UI 状态数据类(聚合所有 UI 所需状态)
    data class LoginUiState(
        val isLoading: Boolean = false,
        val errorMsg: String = "",
        val isLoginSuccess: Boolean = false,
        val token: String = ""
    )
}

Step 4:UI 层(Composable 函数)

仅订阅 ViewModel 状态、触发事件,无任何业务逻辑:

ini 复制代码
@Composable
fun LoginScreen(
    viewModel: LoginViewModel = viewModel(), // 自动注入 ViewModel
    onLoginSuccess: (String) -> Unit // 登录成功后的跳转回调
) {
    // 1. 收集 ViewModel 状态(collectAsState 自动订阅,状态变化触发重组)
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
    // 2. 输入框状态(UI 级临时状态,无需放 ViewModel)
    var username by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        // 用户名输入框
        OutlinedTextField(
            value = username,
            onValueChange = { username = it },
            label = { Text("用户名") },
            singleLine = true,
            modifier = Modifier.fillMaxWidth()
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // 密码输入框
        OutlinedTextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("密码") },
            singleLine = true,
            visualTransformation = PasswordVisualTransformation(),
            modifier = Modifier.fillMaxWidth()
        )
        
        // 错误提示
        if (uiState.errorMsg.isNotBlank()) {
            Text(
                text = uiState.errorMsg,
                color = Color.Red,
                modifier = Modifier.padding(top = 8.dp)
            )
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // 登录按钮(加载中禁用)
        Button(
            onClick = { viewModel.handleLogin(username, password) },
            enabled = !uiState.isLoading,
            modifier = Modifier.fillMaxWidth()
        ) {
            if (uiState.isLoading) {
                CircularProgressIndicator(
                    modifier = Modifier.size(20.dp),
                    strokeWidth = 2.dp
                )
            } else {
                Text("登录")
            }
        }
    }

    // 登录成功:触发跳转(副作用,用 LaunchedEffect 避免重组重复执行)
    LaunchedEffect(uiState.isLoginSuccess) {
        if (uiState.isLoginSuccess) {
            onLoginSuccess(uiState.token)
        }
    }
}

Step 5:Activity 层(仅承载 UI)

kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                LoginScreen(onLoginSuccess = { token ->
                    // 登录成功跳转到首页
                    navController.navigate("home")
                })
            }
        }
    }
}

四、状态设计的核心规则(避坑关键)

1. 状态分类:区分"UI 临时状态"和"业务状态"

状态类型 示例 存放位置 原因
UI 临时状态 输入框内容、列表滚动位置、弹窗显隐 Composable 内(remember) 仅影响单个 UI 组件,无需共享/持久化
业务状态 登录状态、用户信息、接口返回数据 ViewModel(StateFlow) 需跨组件共享、持久化,或随生命周期保活

2. 状态上移:避免局部状态耦合

  • 若多个子组件需要共享状态(如"登录按钮"和"错误提示"共享 isLoading),将状态上移到父组件/ViewModel;
  • 示例:输入框内容是"局部状态",但"登录是否成功"是"全局业务状态",需放在 ViewModel。

3. 不可变状态:用 data class + copy 更新

  • 所有状态数据类(如 LoginUiState)设计为不可变(val),更新时通过 copy 生成新对象;
  • 原因:Compose 依赖"状态对象引用变化"触发重组,可变对象会导致重组失效。

4. 生命周期绑定:用 collectAsStateWithLifecycle

  • 收集 StateFlow 时,优先用 collectAsStateWithLifecycle()(而非 collectAsState()),自动在 Activity/Fragment 进入后台时暂停收集,避免内存泄漏。

五、进阶:MVVM + Clean Architecture(整洁架构)

中大型项目可在 MVVM 基础上引入 Clean Architecture,进一步分层解耦:

markdown 复制代码
┌─────────────────┐
│ Presentation 层 │ UI + ViewModel(与 MVVM 一致)
└─────────┬───────┘
          │
┌─────────▼───────┐
│ Domain 层       │ 用例(UseCase)+ 领域模型(Entity),封装核心业务逻辑
│                 │ 示例:LoginUseCase 封装"校验参数 + 调用仓库 + 处理结果"
└─────────┬───────┘
          │
┌─────────▼───────┐
│ Data 层         │ Repository + 数据源(网络/本地),Domain 层不关心数据来源
└─────────────────┘

核心优势:Domain 层完全独立于 UI 和数据层,业务逻辑可单独测试,且易适配多端(如 Compose Multiplatform 复用 Domain 层)。

六、总结

1.主流架构:Compose 首选「MVVM + 单向数据流」,是官方推荐、适配性最好的方案;

2.状态流转:UI 触发事件 → ViewModel 处理 → Repository 获取数据 → ViewModel 更新状态 → UI 重组;

3.核心规则:状态分类存放、不可变设计、生命周期绑定、状态上移;

4.进阶优化:中大型项目加 Clean Architecture 的 Domain 层,解耦业务逻辑。

相关推荐
2501_915909062 小时前
手机崩溃日志导出的工程化体系,从系统级诊断到应用行为分析的多工具协同方法
android·ios·智能手机·小程序·uni-app·iphone·webview
木风小助理2 小时前
MySQL内存监控深度解析与故障排查实践
android·adb
灰鲸广告联盟2 小时前
APP广告变现定制化解决方案,助力收益提升与用户体验平衡
android·flutter·搜索引擎·ux
Calm5503 小时前
ele表单未输入值提示为英文
前端
帅得不敢出门3 小时前
精简Android SDK(AOSP)的git项目提高git指令速度
android·java·开发语言·git·elasticsearch
2501_937189233 小时前
神马 9.0 2025 最新版源码系统:安全加固 + 二次开发友好
android·源码·开源软件·源代码管理·机顶盒
爪洼守门员3 小时前
前端性能优化
开发语言·前端·javascript·笔记·性能优化
TOYOAUTOMATON3 小时前
GTH系列模组介绍
前端·目标检测·自动化