架构总览
约 811 字大约 3 分钟
2026-03-20
1. 背景与目标
本页先回答一个核心问题:为什么 SleepIn 要分层。
- 对 Android 新手来说,最大难点不是语法,而是“代码该放在哪一层”。
- SleepIn 采用
MVVM + Clean Architecture,目标是让 UI、业务规则、数据存储互相解耦。 - 本页先给结构总览,再给真实调用链,帮助你建立稳定的开发心智模型。
2. 相关模块与文件位置(先看树)
app/src/main/java/com/kurosu/sleepin/
├─ MainActivity.kt # 单 Activity 入口,设置主题并挂载 NavHost
├─ SleepInApplication.kt # 手动 DI 根节点,集中创建 UseCase
├─ data/ # 数据实现层(Room/DataStore/CSV/RepositoryImpl)
├─ domain/ # 业务核心层(Model/Repository 接口/UseCase)
├─ ui/ # 展示层(Screen/ViewModel/Navigation/Compose 组件)
├─ di/
│ ├─ DatabaseModule.kt # Room Database 与 DAO 提供
│ └─ RepositoryModule.kt # Repository 与 CSV 组件提供
└─ widget/ # 小组件与 WorkManager 刷新链路3. 架构总图与依赖方向
graph LR
UI[UI 层] --> Domain[Domain 层]
Data[Data 层] -. 实现接口 .-> Domain
App[SleepInApplication / DI] --> Data
App --> Domain依赖规则(必须遵守):
- UI 可以依赖 Domain,但不能直接依赖 DAO/Entity。
- Domain 不依赖 Android 框架。
- Data 实现 Domain 的 Repository 接口。
SleepInApplication负责在启动时把依赖串起来。
4. 分层讲解(每层先看文件,再看职责)
4.1 Domain 层(业务规则中心)
domain/
├─ model/ # Course、Timetable、Schedule 等领域模型
├─ repository/ # CourseRepository 等接口定义
└─ usecase/ # AddCourseUseCase 等业务动作- 这一层描述“业务上要做什么”,不关心 Room 或 Compose。
- UseCase 通过
operator fun invoke作为统一业务入口,方便 ViewModel 调用和测试。
4.2 Data 层(数据来源与持久化)
data/
├─ local/ # SleepInDatabase、Entity、DAO、Mapper
├─ repository/ # XxxRepositoryImpl 具体实现
├─ preferences/ # DataStore 设置读写
└─ csv/ # CsvImporter/CsvExporter- 负责把数据库结构(Entity)映射成领域结构(Model)。
- 对上层暴露的是 Repository 接口行为,而不是底层 SQL 细节。
4.3 UI 层(状态驱动渲染)
ui/
├─ navigation/SleepInNavHost.kt # 路由与 ViewModel 工厂装配
└─ screen/* # Screen + ViewModel- ViewModel 负责收集 UseCase 数据、产出
StateFlow。 - Compose Screen 只做渲染与事件转发,不承载核心业务规则。
4.4 DI 与应用入口(手动注入)
SleepInApplication.kt
di/DatabaseModule.kt
di/RepositoryModule.kt- 项目当前未启用 Hilt,采用手动 DI。
SleepInApplication中创建Database -> Repository -> UseCase,供导航层取用。
5. 核心流程(主页课程展示调用链)
sequenceDiagram
participant UI as HomeScreen
participant VM as HomeViewModel
participant UC as GetCoursesForTimetableUseCase
participant Repo as CourseRepositoryImpl
participant Dao as CourseDao
UI->>VM: 订阅 uiState
VM->>UC: invoke(timetableId)
UC->>Repo: getCoursesForTimetable(timetableId)
Repo->>Dao: observeByTimetableId(...)
Dao-->>Repo: Flow<List<CourseWithSessionsEntity>>
Repo-->>UC: Flow<List<CourseWithSessions>>
UC-->>VM: Flow
VM-->>UI: HomeUiState 更新,Compose 重组6. 新功能如何落地(标准步骤)
以“新增一个业务功能”举例,按这个顺序做:
- 在
domain/model定义领域模型。 - 在
domain/repository定义接口能力。 - 在
domain/usecase实现业务动作。 - 在
data/local+data/repository落地存储与实现。 - 在
SleepInApplication.kt组装新依赖。 - 在
ui/screen增加 ViewModel 和 Screen,并在ui/navigation/SleepInNavHost.kt接入路由。
7. 调试与排错建议
- 如果页面不刷新,先检查 ViewModel 是否在收集
Flow并映射到StateFlow。 - 如果出现越层调用,优先检查 UI 是否直接依赖了 DAO 或 Entity。
- 如果依赖注入报错,先从
SleepInApplication.kt的初始化链路逐级核对。
8. 延伸阅读与下一步
- 下一篇建议阅读:
docs/SleepIn-Docs/docs/dev/module-responsibilities.md - 然后进入主链细节:
data-model-room.md->business/usecase-chain.md->ui-viewmodel-compose.md
