价格

架构总览

相关源文件

本页面内容基于以下源文件生成:

本项目采用 MVI(Model-View-Intent)架构,基于 Jetpack Compose 构建声明式 UI 层。MVI 架构借鉴了前端框架(如 React/Redux)的思想,强调单向数据流唯一数据源,与 Compose 的声明式 UI 思想高度契合 README.md:17-18

MVI 架构概述

MVI 架构将应用分为三层核心组件:ModelViewIntent,通过严格的单向数据流约束各层职责边界 README.md:17-23

核心组件职责

组件职责关键特征
Model管理 UI 状态(State),如页面加载状态、控件位置等唯一数据源,集中管理所有 UI 状态
ViewUI 承载单元(Activity/Composable),订阅 Model 变化实现界面刷新被动响应状态变化,不直接调用 ViewModel 方法
Intent封装用户操作,发送给 Model 层进行数据请求不是 Activity 的 Intent,而是用户意图的抽象

与 MVVM 的关键差异

MVI 在 MVVM 基础上进行了架构约束强化 README.md:60-62

  1. 交互方式约束:MVVM 允许 View 层随意调用 ViewModel 方法,而 MVI 屏蔽 ViewModel 实现,只能通过发送 Intent 驱动事件
  2. 状态集中管理:MVVM 在 ViewModel 中分散定义多个 State,MVI 使用 ViewState 集中管理,只需订阅一个 ViewState 即可获取页面所有状态
正在加载图表渲染器...

架构图要点说明

  1. 单向数据流:用户操作 → Intent → StateProcessor → ViewState → View 更新,形成闭环
  2. 唯一数据源:ViewState 作为唯一状态源,所有 UI 组件订阅同一状态实例
  3. 事件分离:ViewEvent 处理 Toast、页面关闭等一次性事件,与持续性状态 ViewState 分离
  4. ViewModel 屏蔽:View 层不直接调用 ViewModel 方法,通过 Intent 解耦

核心模块详解

状态管理模块

职责边界:集中管理页面所有 UI 状态,作为唯一数据源供 View 层订阅。不包含业务逻辑,仅负责状态定义与存储。

入口与关键 API

  • 数据类:LoginViewState(登录页面状态定义)README.md:30-34
  • 状态字段:account(账号)、password(密码)、isLogged(登录状态)

关键数据结构

kotlin
1data class LoginViewState(
2    val account: String = "",      // 用户输入的账号
3    val password: String = "",     // 用户输入的密码
4    val isLogged: Boolean = false  // 登录成功标志
5)

交互方式:View 层通过 Compose 的 collectAsState() 订阅 ViewState 变化,状态更新自动触发 UI 重组。

意图封装模块

职责边界:将所有用户操作封装为类型安全的 Intent 对象,定义 View 层可触发的操作范围。不处理操作执行逻辑。

入口与关键 API

  • 密封类:LoginViewAction(登录页面操作定义)README.md:47-53
  • 操作类型:Login(登录)、ClearAccount(清空账号)、ClearPassword(清空密码)、UpdateAccount(更新账号)、UpdatePassword(更新密码)

关键数据结构

kotlin
1sealed class LoginViewAction {
2    object Login : LoginViewAction()                              // 登录操作
3    object ClearAccount : LoginViewAction()                       // 清空账号
4    object ClearPassword : LoginViewAction()                      // 清空密码
5    data class UpdateAccount(val account: String) : LoginViewAction()    // 更新账号
6    data class UpdatePassword(val password: String) : LoginViewAction()  // 更新密码
7}

交互方式:View 层用户操作触发后,包装为 ViewAction 发送给 ViewModel 层的 StateProcessor 处理 README.md:55-58

事件处理模块

职责边界:处理一次性事件(如 Toast 提示、页面导航),避免状态回退导致事件重复触发。不管理持续性 UI 状态。

入口与关键 API

  • 密封类:LoginViewEvent(一次性事件定义)README.md:39-42
  • 事件类型:PopBack(返回上一页)、ErrorMessage(错误提示)

关键数据结构

kotlin
1sealed class LoginViewEvent {
2    object PopBack : LoginViewEvent()                           // 页面返回事件
3    data class ErrorMessage(val message: String) : LoginViewEvent()  // 错误消息事件
4}

错误处理与边界条件

  • 事件消费后自动移除,避免配置变更(如屏幕旋转)后重复触发
  • 使用 SingleLiveEventSharedFlow 确保事件仅被消费一次

视图层模块

职责边界:作为 UI 承载单元(Activity/Composable),订阅 ViewState 变化实现界面刷新。不包含业务逻辑,不直接调用 ViewModel 方法。

入口与关键 API

  • Composable 函数:页面级 UI 组件
  • 订阅方式:collectAsState() 订阅 ViewState

交互方式

  1. 订阅 ViewState 获取页面所有状态 README.md:22
  2. 用户操作包装为 ViewAction 发送给 ViewModel
  3. 接收 ViewEvent 处理一次性事件

数据流与调用链

用户登录完整数据流

以下时序图展示了用户点击登录按钮后的完整数据流:

正在加载图表渲染器...

数据流要点说明

  1. Intent 驱动:所有用户操作必须包装为 ViewAction,通过 Intent 机制驱动状态变更 README.md:23
  2. 单向流动:数据流方向为 View → Intent → Processor → State → View,形成闭环
  3. 状态优先:ViewState 变化自动触发 UI 重组,无需手动更新 UI
  4. 事件分离:一次性事件通过 ViewEvent 处理,避免状态回退导致重复触发

状态更新调用链

正在加载图表渲染器...

调用链要点说明

  1. 不可变状态:使用 data classcopy() 方法创建新状态实例,保证状态不可变性 README.md:30-34
  2. Reducer 模式:通过纯函数计算新状态,输入当前状态 + Action,输出新状态
  3. 响应式更新:Compose 自动检测状态变化,仅重组受影响的 UI 组件

核心设计决策与取舍

1. 选择 MVI 而非 MVVM

决策理由

  • MVI 的单向数据流与 Compose 声明式 UI 思想高度契合 README.md:64
  • ViewState 集中管理减少模板代码,只需订阅一个 State 即可获取页面所有状态 README.md:62
  • Intent 机制约束 View 与 ViewModel 交互方式,降低耦合度 README.md:61

已知限制

  • 简单页面可能引入过多模板代码(ViewState、ViewAction、ViewEvent 定义)
  • 状态集中管理可能导致 ViewState 类过大,需要合理拆分

2. 使用密封类定义 Intent 和 Event

决策理由

  • 类型安全:编译期检查所有可能的操作/事件类型
  • 穷尽匹配:when 表达式强制处理所有分支,避免遗漏
  • 可扩展:新增操作/事件只需添加子类,不影响现有代码

关键实现sealed class LoginViewActionsealed class LoginViewEvent README.md:39-53

3. ViewState 使用 data class

决策理由

  • 自动生成 copy() 方法,支持不可变状态更新
  • 自动生成 equals()/hashCode(),便于状态比较和去重
  • 解构声明支持,便于提取状态字段

关键实现data class LoginViewState README.md:30-34

4. 分离持续性状态与一次性事件

决策理由

  • ViewState 管理持续性状态(如文本内容、加载状态)
  • ViewEvent 处理一次性事件(如 Toast、页面跳转)
  • 避免配置变更后事件重复触发

关键实现sealed class LoginViewEvent README.md:39-42

5. ViewModel 实现对 View 层屏蔽

决策理由

  • View 层只能通过发送 Intent 驱动事件,不能直接调用 ViewModel 方法 README.md:61
  • 降低 View 与 ViewModel 耦合度,便于单元测试
  • 统一交互入口,便于添加日志、监控等横切关注点

技术选型

技术用途选型理由替代方案
MVI 架构应用架构模式单向数据流与 Compose 声明式 UI 契合,状态集中管理减少模板代码MVVM、MVP
Jetpack ComposeUI 框架声明式 UI,与 MVI 架构思想一致,减少 UI 样板代码XML + View System
Kotlin Sealed ClassIntent/Event 定义类型安全,编译期检查,穷尽匹配Enum、普通类
Kotlin Data ClassViewState 定义自动生成 copy/equals/hashCode,支持不可变状态普通类 + 手动实现
StateFlow状态流管理热流,支持多订阅者,与 Compose collectAsState 无缝集成LiveData、RxJava
SharedFlow一次性事件流支持配置 replay=0 避免事件重复消费LiveData、Channel
Hilt依赖注入编译期生成代码,性能优于运行时反射Koin、Dagger2
Retrofit网络请求类型安全的 HTTP 客户端,支持 Kotlin 协程Ktor、OkHttp

模块依赖关系

正在加载图表渲染器...

依赖关系要点说明

  1. 单向依赖:UI 层依赖 MVI 架构层,MVI 层依赖 ViewModel 层,ViewModel 层依赖数据层,依赖方向清晰
  2. MVI 组件解耦:ViewState、ViewAction、ViewEvent 相互独立,通过 ViewModel 中的 StateProcessor 协调
  3. 数据层抽象:Repository 屏蔽数据来源(API/DB),ViewModel 只依赖 Repository 接口

关键配置与启动流程

MVI 核心组件配置

ViewState 定义规范 README.md:30-34

  • 使用 data class 定义页面所有状态
  • 为所有字段提供默认值,避免空指针异常
  • 状态字段应包含 UI 显示所需的所有数据

ViewAction 定义规范 README.md:47-53

  • 使用 sealed class 定义所有用户操作
  • object 用于无参数操作,data class 用于带参数操作
  • 操作命名应清晰表达用户意图

ViewEvent 定义规范 README.md:39-42

  • 使用 sealed class 定义所有一次性事件
  • 仅包含需要消费一次的事件(如 Toast、导航)
  • 持续性状态应放在 ViewState 中管理

启动流程

  1. 初始化 ViewState:ViewModel 创建时初始化默认状态
  2. 订阅状态:Composable 通过 collectAsState() 订阅 ViewState
  3. 监听操作:Composable 用户操作触发 ViewAction 发送
  4. 处理操作:ViewModel 接收 ViewAction,调用 StateProcessor 处理
  5. 更新状态:StateProcessor 计算新状态,发射新 ViewState
  6. UI 重组:Compose 检测状态变化,自动重组受影响的 UI 组件

架构选型建议

MVI 与 Compose 的声明式 UI 思想高度契合,理论上应该是 Compose 的最佳伴侣 README.md:64。但 MVI 也只是在 MVVM 基础上做了一定改良,MVVM 也可以很好地配合 Compose 使用,开发者可根据项目需要选择合适的架构 README.md:65