10 KiB
Android 系统架构
状态:已接受的 MVP 架构 技术方向:Kotlin + Jetpack Compose,单模块起步 骨架来源:Android Architecture Starter Template — base
1. 架构目标
- 本地起卦是确定、可测试、无 Android/网络依赖的纯领域逻辑。
- UI、内容、存储和 AI 可以独立替换,不改变已生成的
CastResult。 - 核心流程离线可用,AI 故障只降级解释能力。
- 包边界对人和编码代理都清楚,并能通过测试或静态规则检查。
- MVP 保持单 Gradle 模块,避免过早多模块化;代码仍按领域和层分包。
Android 官方建议新应用采用清晰的 UI/数据分层、单向数据流、ViewModel、协程与 Flow;大型复杂业务才按需要增加 domain 层。本项目因为起卦不变量重要,保留轻量 domain 层。Android 架构指南
2. 系统上下文
┌──────────────── Android App ────────────────┐
│ Compose UI │
│ ↓ intent ↑ immutable state │
│ ViewModel / state holder │
│ ↓ │
│ Domain: CastEngine / explanation use cases │
│ ↓ interfaces │
│ Repositories │
│ ├─ Assets / prepackaged content │
│ ├─ Room history │
│ ├─ DataStore settings │
│ └─ AI gateway ───────────────→ App backend│
└─────────────────────────────────────────────┘
只有 AI 解释需要网络。起念、投币、起卦、卦库读取和本地解释都位于 Android 应用内部。
3. 建议目录
工程初始化后建议采用:
app/src/main/java/<package>/
├── app/
│ ├── BrainwaveApplication.kt
│ ├── MainActivity.kt
│ └── BrainwaveNavHost.kt
├── core/
│ ├── model/ # 跨层不可变领域模型
│ ├── designsystem/ # 主题、字体、间距、卦象 Canvas
│ └── common/ # 极少量通用结果类型/调度器
├── domain/
│ ├── casting/ # CastEngine、映射与校验
│ └── explanation/ # 解释用例与端口
├── data/
│ ├── content/ # assets 卦库解析与版本校验
│ ├── history/ # Room entity、DAO、repository
│ ├── settings/ # DataStore
│ └── ai/ # 网络 DTO、gateway、响应校验
└── feature/
├── onboarding/
├── home/ # 回访首页、最近一次、已上线次级入口
├── question/
├── casting/
├── result/
├── explanation/
├── history/
└── settings/
测试按相同包结构镜像放入 src/test 和 src/androidTest。
4. 依赖方向
允许:
feature UI → feature ViewModel → domain use case → repository interface
data implementation → repository interface + core model
app/navigation → feature public route
core/designsystem → Compose/Material + core model(仅绘制需要)
禁止:
domain依赖 Compose、Activity、ViewModel、Room、Retrofit/Ktor 或具体 AI SDK。CastEngine读取系统时间、随机数、网络、数据库或问题文本。- Composable 直接访问 DAO、assets、网络客户端或 Hilt entry point。
data/ai引用或调用CastEngine。- AI DTO 直接成为 UI 状态;必须先校验并映射为领域结果。
- feature 之间直接引用对方的内部 ViewModel 或 screen 实现。
- 一个“Utils”包承载无边界的杂项业务逻辑。
工程具备代码后,应通过架构测试或静态检查机械执行这些禁止项,不能只依赖评审记忆。
5. 核心组件职责
CastEngine
纯 Kotlin、无副作用。输入六轮铜币和方法版本,输出不可变 CastResult。所有规则来自领域规则。
HexagramContentRepository
按卦号和 contentVersion 返回已校验的本地内容。缺失、重复或版本不兼容属于数据完整性错误,不使用 AI 猜补。
CastingSessionViewModel
维护草稿问题、六轮录入和状态机。通过 SavedStateHandle 保存可恢复的进行中状态;只在第六轮确认时调用 CastEngine。
HistoryRepository
接收第六爻锁定事件与当次 HistorySavePolicy 快照,在 autoSaveHistory=true 时持久化会话;总开关关闭时仍支持结果页明确发出的“保存本次”命令。策略包含 saveQuestion、saveExplanation 和 saveActionNote,生产默认值均为 true,由 DataStore 提供但不得泄露到领域起卦逻辑。
Repository 提供倒序历史与最新一条的 Flow 查询,并把随后产生的本地或 AI 解读事务性关联到同一 session。重新解读新增版本或执行经过明确确认的替换命令,禁止普通 upsert 静默覆盖。DAO 事务负责 session、explanation 和 action 的一致写入与级联删除;首页直接订阅最新记录的投影,不维护第二份敏感正文缓存。Room 模型不得泄露到 UI;数据库迁移必须有测试。
HomeViewModel / HistoryViewModel
HomeViewModel 组合 onboarding 状态、最新历史投影、保存策略摘要和已启用功能入口,只输出真实可进入的入口;不为未来功能生成禁用占位。首页固定输出本机保存/AI 发送边界文案和设置入口。HistoryViewModel 维护列表、空态、详情、单条删除和清空全部确认状态,通过 HistoryRepository 完成操作。导航使用一次性 UI 事件或 NavHost 回调,返回后恢复列表位置。
ExplanationRepository
提供统一接口:本地解释和 AI 解释返回相同的可展示领域结构,并带明确 source。AI 实现遵循AI 解释与安全。
6. 单向数据流
每个 feature 使用不可变 UiState 和显式 UiAction:
user action → ViewModel → use case/repository → state update → Compose render
一次性结果也应建模为状态及消费动作,避免裸 Channel/事件在配置变化时丢失。导航由 UI 根据已处理状态触发,并保证重复收集不会重复提交 AI 请求。
领域计算成功后 CastResult 为只读值。解释状态与起卦状态分开存储:
ResultUiState(
castResult = immutable,
content = immutable,
explanationState = Idle | Loading | Local | Ai | Failed
)
7. 数据和存储选择
- assets 或预置 Room:版本化的 64 卦及爻辞。首版如果仅按编号读取,JSON assets 更简单;需要全文检索或内容迁移时再采用预置 Room。
- Room:按保存策略自动写入或由用户当次明确保存的会话、解释和行动记录。Android 官方推荐 Room 管理非平凡结构化数据。Room 文档
- DataStore:方法说明是否已读、主题、AI 同意版本和历史保存策略等少量设置;不保存大型历史或完整卦库。DataStore 文档
- SavedStateHandle:恢复当前未完成流程;不是长期历史数据库。
历史 Room 数据库及其辅助文件必须通过 Android backup/data-extraction 配置排除系统自动备份和设备迁移。此边界由构建配置测试机械验证,不能只依赖界面文案;引入导出、账号或同步前必须新增 ADR。
详细契约见数据与内容。
8. 网络与 AI 边界
正式发行版本通过自有后端代理模型服务:
App → HTTPS backend → model provider
开发者密钥不能放入 APK。网络层只在用户主动选择 AI 解读后工作,设置合理连接/读取超时、取消传播和有限重试。不得后台预取解释,不得在用户继续编辑问题时偷偷重发。
如果未来支持用户自带密钥,它是单独的产品模式,需要 Android Keystore、清晰风险说明和独立决策记录,不能与默认发布路径混合。
9. 安全与隐私
- 仅请求联网所需权限;起卦不需要相机、定位、联系人、传感器或存储权限。
- 所有请求使用 TLS,服务端执行认证、限流和滥用保护。
- 日志不得包含问题原文、完整提示词、模型回复或 API 密钥。
- 崩溃与分析事件只记录枚举状态、耗时桶和匿名错误码。
- 用户删除本地记录后,不保留隐藏副本;服务端数据保留策略需在 AI 上线前单独确认。
- 任何来自 assets、数据库或网络的数据在边界处解析和校验,失败后进入有恢复路径的错误状态。
10. 依赖和构建政策
- 版本集中在 Gradle Version Catalog。
- 优先使用 AndroidX、Kotlin 官方组件和维护活跃的小型依赖。
- 新依赖必须说明用途、维护状态、许可证、体积和是否可由现有能力替代。
- 禁止直接引入完整“算命 SDK”或无法审计的卦象计算库。
- Compose 依赖使用 BOM 对齐版本。
compileSdk/targetSdk使用实现时最新稳定且满足商店要求的版本,不在方案文档硬编码会迅速过期的数值。minSdk默认提案为 26,最终值见决策记录。
11. 多模块化触发条件
满足以下至少两个条件后再评估从 base 迁移为多模块:
- 两名以上开发者长期并行修改独立 feature;
- 构建时间已经影响反馈循环;
- 有可独立发布/复用的设计系统或领域库;
- 历史、内容浏览、账户等功能使单模块边界难以机械约束;
- 需要独立基准测试或测试应用。
迁移前必须新增 ADR;“项目看起来更专业”不是拆模块理由。