Connect You 开源联系人应用开发者指南:Jetpack Compose + Room 架构完整解析
【免费下载链接】ConnectYouPrivacy focused contacts and SMS messenger app built with MD3项目地址: https://gitcode.com/gh_mirrors/co/ConnectYou
Connect You 是一款注重隐私、基于 Material Design 3 的开源 Android 联系人管理 + 短信应用,完整采用 Jetpack Compose 构建 UI、Room 管理本地数据。本文带你从目录结构到 Repository 层双数据源设计,快速看懂这套 MVVM 架构是如何落地的。
项目分层架构一览:先看目录布局
整个应用只有一个模块app,但内部严格按照「数据层 → 领域层 → 表现层」三层组织,位于 app/src/main/java/com/bnyro/contacts/ 下:
| 层 | 目录 | 职责 |
|---|---|---|
| 表现层 | presentation/ | Compose 界面、ViewModel、可复用组件 |
| 导航层 | navigation/ | 路由定义与深链处理 |
| 领域层 | domain/ | 数据模型、枚举、Repository 接口 |
| 数据层 | data/ | Room 数据库与 DAO |
| 工具层 | util/ | 备份、权限、联系人工具类 |
这种布局的好处是:界面只依赖 ViewModel,ViewModel 只依赖 Repository 接口,换数据源时界面代码零改动——这正是后文「双数据源」设计的价值所在。
Room 数据库设计:3 张表存下所有联系人数据
核心数据库定义在 AppDatabase.kt,当前版本为 8,并声明了 2→3→4→5 与 6→7→8 的多段 AutoMigration:
@Database( entities = [LocalContact::class, DbDataItem::class, SmsData::class], autoMigrations = [ AutoMigration(2, 3), AutoMigration(3, 4), // ... ], version = 8 ) abstract class AppDatabase : RoomDatabase()设计上有两个值得学习的点:
- EAV 模式存属性:联系人主表
LocalContact只存姓名、机构、收藏位等固定字段;手机号、邮箱、生日、备注等可变数量的属性统一放进DbDataItem表,用category字段(枚举 DataCategory.kt 中的 NUMBER / EMAIL / EVENT / NOTE…)区分类型。这样新增一种属性类型不需要改表结构。 - AutoMigration 声明式升级:版本升级只靠注解驱动,数据库的完整演进历史可在 app/schemas/ 目录看到 2 到 8 共 7 个版本的 schema 快照 JSON,方便做数据校验。
Repository 双数据源:本地库 vs 系统联系人
这是全项目最有架构价值的部分。领域层定义了统一接口 ContactsRepository.kt:
interface ContactsRepository { val label: String suspend fun createContact(contact: ContactData) suspend fun getContactList(): List<ContactData> // ... }两个实现各走一条完全不同的技术路线:
| 实现 | 存储位置 | 技术要点 |
|---|---|---|
| LocalContactsRepository.kt | 应用私有 Room 库 | 头像存文件、属性存 DbDataItem 表 |
| DeviceContactsRepository.kt | 系统 ContactsContract | ContentProvider 批量操作 + 权限校验 |
- 本地实现:头像不进数据库,而是以联系人 ID 命名写入
filesDir/images目录(见saveProfileImage),避免了 Room 中大图片的性能问题。 - 设备实现:所有写入都通过
ContentProviderOperation组装成批量操作再applyBatch,并用@RequiresPermission注解约束READ_CONTACTS/WRITE_CONTACTS权限。 - 切换逻辑在 ViewModel:ContactsModel.kt 中通过
contactsSource状态决定当前注入哪个实现,UI 层对数据从哪来完全无感知。
MVVM + Compose 状态管理:ViewModel 怎么做
表现层每个屏幕对应一个ViewModel(presentation/screens/),以联系人列表的 ContactsModel.kt 为例:
- 用
mutableStateOf直接驱动 Compose 重组:contactsSource by mutableStateOf(...),用户切换「本地 / 设备」来源时 UI 自动刷新。 - UI 状态用密封类建模:列表状态 ContactListState.kt 定义
Loading / Success / Error等形态,界面只需when分发,杜绝 null 判断散落各处的经典问题。 - 并行加载:初始化时通过
async+awaitAll并发拉取两个数据源,配合Dispatchers.IO执行耗时 IO。 - 无 Hilt,手工依赖注入:ViewModel 通过
viewModelFactory { initializer { ... } }手动构造并传入两个 Repository,保持轻量无注解处理器。
导航与深链:可序列化的路由对象
路由定义在 NavRoutes.kt,所有路由都是@Serializable密封类,参数直接作为类字段:
@Serializable data class MessageThread( val address: String, val body: String? = null ) : NavRoutes()它还内置了connectyou://message/<号码>这类深链(navDeepLink),允许从快捷方式或系统短信意图直接跳转到对应会话——导航配置见 NavContainer.kt。
隐私优先的配套设计
- 自动备份:BackupWorker.kt 定时导出,VcardHelper.kt 负责 vCard 导入导出,ZipUtils.kt 打包并可选密码加密(PasswordUtils.kt)。
- 本地短信收件箱:MmsReceiver.kt 捕获短信/MMS 写入 Room 的
SmsData表,形成独立于系统短信应用的私有存档。 - 生物识别:BiometricAuthUtil.kt 为打开加密备份提供指纹/面容解锁。
总结:这套架构最值得抄作业的 3 个点
- 接口先行:Repository 接口放在 domain 层,Room 与系统 ContentProvider 两种数据源对上层透明,测试和扩展都容易。
- EAV 存属性:用
category + value的通用表替代宽表,新增联系人字段类型零 schema 变更。 - 密封类建模 UI 状态 + 手动工厂注入:不引入重量级 DI 框架,仅用 Compose State 和 ViewModel 就实现了清晰的单向数据流。
按「先读 ContactsRepository.kt 接口 → 再对比两个实现 → 最后看 ViewModel 如何消费」的顺序走一遍源码,半天时间就能完整理解这个项目的架构脉络,也为你自己的 Android 联系人/数据管理类应用提供了一套可直接参考的模板。
【免费下载链接】ConnectYouPrivacy focused contacts and SMS messenger app built with MD3项目地址: https://gitcode.com/gh_mirrors/co/ConnectYou
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考