docs: add Android app implementation harness
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
# Android 系统架构
|
||||
|
||||
> 状态:已接受的 MVP 架构
|
||||
> 技术方向:Kotlin + Jetpack Compose,单模块起步
|
||||
> 骨架来源:[Android Architecture Starter Template — base](https://github.com/android/architecture-templates/tree/base)
|
||||
|
||||
## 1. 架构目标
|
||||
|
||||
- 本地起卦是确定、可测试、无 Android/网络依赖的纯领域逻辑。
|
||||
- UI、内容、存储和 AI 可以独立替换,不改变已生成的 `CastResult`。
|
||||
- 核心流程离线可用,AI 故障只降级解释能力。
|
||||
- 包边界对人和编码代理都清楚,并能通过测试或静态规则检查。
|
||||
- MVP 保持单 Gradle 模块,避免过早多模块化;代码仍按领域和层分包。
|
||||
|
||||
Android 官方建议新应用采用清晰的 UI/数据分层、单向数据流、ViewModel、协程与 Flow;大型复杂业务才按需要增加 domain 层。本项目因为起卦不变量重要,保留轻量 domain 层。[Android 架构指南](https://developer.android.com/topic/architecture)
|
||||
|
||||
## 2. 系统上下文
|
||||
|
||||
```text
|
||||
┌──────────────── 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. 建议目录
|
||||
|
||||
工程初始化后建议采用:
|
||||
|
||||
```text
|
||||
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/
|
||||
├── question/
|
||||
├── casting/
|
||||
├── result/
|
||||
├── explanation/
|
||||
├── history/
|
||||
└── settings/
|
||||
```
|
||||
|
||||
测试按相同包结构镜像放入 `src/test` 和 `src/androidTest`。
|
||||
|
||||
## 4. 依赖方向
|
||||
|
||||
允许:
|
||||
|
||||
```text
|
||||
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`。所有规则来自[领域规则](domain-rules.md)。
|
||||
|
||||
### `HexagramContentRepository`
|
||||
|
||||
按卦号和 `contentVersion` 返回已校验的本地内容。缺失、重复或版本不兼容属于数据完整性错误,不使用 AI 猜补。
|
||||
|
||||
### `CastingSessionViewModel`
|
||||
|
||||
维护草稿问题、六轮录入和状态机。通过 `SavedStateHandle` 保存可恢复的进行中状态;只在第六轮确认时调用 `CastEngine`。
|
||||
|
||||
### `HistoryRepository`
|
||||
|
||||
在用户明确保存后持久化会话。Room 模型不得泄露到 UI;数据库迁移必须有测试。
|
||||
|
||||
### `ExplanationRepository`
|
||||
|
||||
提供统一接口:本地解释和 AI 解释返回相同的可展示领域结构,并带明确 `source`。AI 实现遵循[AI 解释与安全](ai-safety.md)。
|
||||
|
||||
## 6. 单向数据流
|
||||
|
||||
每个 feature 使用不可变 `UiState` 和显式 `UiAction`:
|
||||
|
||||
```text
|
||||
user action → ViewModel → use case/repository → state update → Compose render
|
||||
```
|
||||
|
||||
一次性结果也应建模为状态及消费动作,避免裸 `Channel`/事件在配置变化时丢失。导航由 UI 根据已处理状态触发,并保证重复收集不会重复提交 AI 请求。
|
||||
|
||||
领域计算成功后 `CastResult` 为只读值。解释状态与起卦状态分开存储:
|
||||
|
||||
```text
|
||||
ResultUiState(
|
||||
castResult = immutable,
|
||||
content = immutable,
|
||||
explanationState = Idle | Loading | Local | Ai | Failed
|
||||
)
|
||||
```
|
||||
|
||||
## 7. 数据和存储选择
|
||||
|
||||
- **assets 或预置 Room**:版本化的 64 卦及爻辞。首版如果仅按编号读取,JSON assets 更简单;需要全文检索或内容迁移时再采用预置 Room。
|
||||
- **Room**:用户主动保存的会话、解释和行动记录。Android 官方推荐 Room 管理非平凡结构化数据。[Room 文档](https://developer.android.com/training/data-storage/room)
|
||||
- **DataStore**:方法说明是否已读、主题、AI 同意版本等少量设置;不保存大型历史或完整卦库。[DataStore 文档](https://developer.android.com/topic/libraries/architecture/datastore)
|
||||
- **SavedStateHandle**:恢复当前未完成流程;不是长期历史数据库。
|
||||
|
||||
详细契约见[数据与内容](data-content.md)。
|
||||
|
||||
## 8. 网络与 AI 边界
|
||||
|
||||
正式发行版本通过自有后端代理模型服务:
|
||||
|
||||
```text
|
||||
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,最终值见[决策记录](decisions.md#未决问题)。
|
||||
|
||||
## 11. 多模块化触发条件
|
||||
|
||||
满足以下至少两个条件后再评估从 `base` 迁移为多模块:
|
||||
|
||||
- 两名以上开发者长期并行修改独立 feature;
|
||||
- 构建时间已经影响反馈循环;
|
||||
- 有可独立发布/复用的设计系统或领域库;
|
||||
- 历史、内容浏览、账户等功能使单模块边界难以机械约束;
|
||||
- 需要独立基准测试或测试应用。
|
||||
|
||||
迁移前必须新增 ADR;“项目看起来更专业”不是拆模块理由。
|
||||
Reference in New Issue
Block a user