当前位置: 首页 > news >正文

Connect You 开源联系人应用开发者指南:Jetpack Compose + Room 架构完整解析

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系统 ContactsContractContentProvider 批量操作 + 权限校验
  • 本地实现:头像不进数据库,而是以联系人 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 为例:

  1. mutableStateOf直接驱动 Compose 重组contactsSource by mutableStateOf(...),用户切换「本地 / 设备」来源时 UI 自动刷新。
  2. UI 状态用密封类建模:列表状态 ContactListState.kt 定义Loading / Success / Error等形态,界面只需when分发,杜绝 null 判断散落各处的经典问题。
  3. 并行加载:初始化时通过async+awaitAll并发拉取两个数据源,配合Dispatchers.IO执行耗时 IO。
  4. 无 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 个点

  1. 接口先行:Repository 接口放在 domain 层,Room 与系统 ContentProvider 两种数据源对上层透明,测试和扩展都容易。
  2. EAV 存属性:用category + value的通用表替代宽表,新增联系人字段类型零 schema 变更。
  3. 密封类建模 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),仅供参考

http://www.cnnetsun.cn/news/4174392.html

相关文章:

  • 从斯大林排序算法看算法正确性与数据完整性
  • 猫抓 Cat-Catch:免费的网页视频资源嗅探与流媒体下载扩展
  • BBDown 命令行下载器:一条命令把 B 站视频存成本地 MP4
  • DRF Docs 安全指南:HIDE_DOCS 配置全解,为什么生产环境必须隐藏 API 文档
  • 多元分数多项式为何衰落?从统计建模稳定性与机器学习范式演变谈起
  • gogstash源码解析(三):codec编解码机制与simpleQueue队列暂停恢复的背压设计
  • SceneKit节点克隆与材质独立难题:Shinkansen 3D Seat Booking Prototype的NodeFactory深克隆技巧
  • 美赛微分方程建模实战:从识别到求解的完整指南
  • rack-tracker 埋点中间件安全深度解析:从 XSS 防护到线程安全的完整设计指南
  • 腾讯前端面试核心考点:JS基础与框架原理解析
  • LÖVE Potion架构深度剖析:modules/objects/utilities三层设计,LÖVE框架移植方法论全解读
  • TP6-Vue-Admin:ThinkPHP6 后台 + Vue 管理后台,前后端分离后台管理系统快速搭建指南
  • 分布感知算法设计:LLM智能体如何根据数据特征优化算法性能
  • 数学建模实战:从数据清洗到趋势预测,解析全球变暖问题的数据科学方法论
  • 突破大数据处理瓶颈:Awesome Data Analysis收录20个高性能工具,Polars、Dask一网打尽
  • 美团大模型产品岗面试全解析:技术考察与业务场景
  • KeplerMapper Cover类深度讲解:n_cubes与perc_overlap如何决定图的精细度
  • 递归算法面试全攻略:从基础到高阶优化
  • BongoCat 互动桌宠快速上手指南:键盘、鼠标、手柄全响应
  • 开源iOS投屏工具:有线优先、低延迟、可控制的开发测试利器
  • 《我的世界》基岩版物品复制机制解析与风险规避指南
  • 基于Wald-SPRT与校准检测的多智能体序列化共识系统设计与实现
  • Rufus 4.0 制作 U 盘启动盘:绕开 Windows 11 TPM 2.0 检查的完整流程
  • GPT-NeoXT-Chat-Base-20B 终极拆解:41GB 五分片权重与 index.json 映射完全指南
  • 如何看懂ProCapNet NPU的预测结果?profile_logits与count_logits一次讲清
  • 贝叶斯机器学习中CRPS:评估概率预测准确性与不确定性的核心指标
  • 把 ECU 软件交给 openAUTOSAR 经典平台:一条能走通的入门路线
  • FlutterFFmpeg 快速上手:10 分钟在移动端集成 FFmpeg,8 种包变体与 LTS 版本一次讲清
  • TERRA触觉反馈设计:用DRV2605L震动马达无声传达“快到了“的信号
  • thinkfan守护进程与信号机制深度剖析:SIGHUP配置热重载、fork双次启动与PID文件防重入设计