剖析 Connect You 短信模块源码:BroadcastReceiver 与 HeadlessSmsSendService 如何实现离线收发短信
【免费下载链接】ConnectYouPrivacy focused contacts and SMS messenger app built with MD3项目地址: https://gitcode.com/gh_mirrors/co/ConnectYou
Connect You 是一款注重隐私的开源联系人 + 短信应用,采用 Material Design 3 风格构建。它的短信模块完全运行在设备本地:不依赖任何第三方服务器,收信靠 Android 系统广播BroadcastReceiver,发信靠系统SmsManager加回执接收器,配合一个名为HeadlessSmsSendService的无头服务,实现了真正的"离线收发短信"。本文带你逐层拆解这套机制,新手也能看懂。
短信模块全景:4 个 Receiver + 1 个 Service
整个短信收发链路由以下几类组件协作完成,核心代码集中在app/src/main/java/com/bnyro/contacts/util/目录下:
| 组件 | 位置 | 职责 |
|---|---|---|
SmsReceiver | app/src/main/java/com/bnyro/contacts/util/receivers/SmsReceiver.kt | 接收系统SMS_DELIVER广播,入库并弹通知 |
ReplyReceiver | app/src/main/java/com/bnyro/contacts/util/receivers/ReplyReceiver.kt | 处理通知栏"快速回复" |
SmsSentReceiver | app/src/main/java/com/bnyro/contacts/util/receivers/SmsSentReceiver.kt | 接收发送回执,更新状态 |
SmsDeliveredReceiver | app/src/main/java/com/bnyro/contacts/util/receivers/SmsDeliveredReceiver.kt | 接收对方送达回执,更新状态 |
HeadlessSmsSendService | app/src/main/java/com/bnyro/contacts/util/services/HeadlessSmsSendService.kt | 声明为短信类应用,支持系统快速回复 |
一条短信从"进入手机"到"发出并回执",完整闭环如下:
收到短信: 基带 → SMS_DELIVER 广播 → SmsReceiver → 系统短信库 → 通知栏(可回复/删除) 发出短信: 用户点击发送 → SmsUtil.sendSms() → 先入库 → SmsManager.sendTextMessage() → SmsSentReceiver(发送回执) → SmsDeliveredReceiver(送达回执) → 状态更新收短信:BroadcastReceiver 如何拦截 SMS_DELIVER 广播
Android 收到短信后,会向系统广播一条android.provider.Telephony.SMS_DELIVER广播。Connect You 的 SmsReceiver 就是专门监听这个广播的"守门员",它的处理流程分三步:
- 过滤广播:
onReceive第一行就检查intent.action != SMS_DELIVER,只处理短信到达事件,其余广播直接忽略; - 解析并入库:用
Telephony.Sms.Intents.getMessagesFromIntent()拆出短信内容(长短信会拆成多条,需要逐条处理),取出号码、正文、时间戳,通过仓库层smsRepo.getOrCreateThreadId()找到对话线程 ID,再调用persistSms()写入系统短信数据库; - 弹出可交互通知:入库后立刻构建一条 Material 风格的
MessagingStyle通知,包含三个动作按钮——"回复"(带RemoteInput输入框)、"删除",以及一个隐藏彩蛋:如果短信正文中检测到 6 位数字验证码(正则(?<!\d)\d{6}(?!\d),见 SmsReceiver.kt#L176),会额外加一个"一键复制验证码"按钮。
值得一提的是,通知里的每个按钮都不是普通跳转,而是通过PendingIntent指向另一个BroadcastReceiver:点"回复"触发 ReplyReceiver,它取出用户输入的文本后,在 IO 协程中调用SmsUtil.sendSms()直接发出回复——整个过程不需要打开应用界面,这就是"通知栏回短信"的实现原理。
发短信:先入库再发送,用两个回执 Receiver 跟踪状态
发送逻辑的核心是 SmsUtil 这个工具对象,关键方法有:
sendSms()(第 46-74 行):发送前先检查信号(ConnectionHelper.hasSignalForSms()),然后把消息以SmsStatus.NONE状态持久化进短信库,拿到自增 ID 后再真正发送;sendSmsInternal()(第 92-115 行):构造两个PendingIntent回执——"已发送"指向SmsSentReceiver,"已送达"指向SmsDeliveredReceiver,连同短信 ID 一起传给SmsManager.sendTextMessage();- 双卡支持:
getSmsManager()通过SmsManager.getSmsManagerForSubscriptionId()指定用哪张 SIM 卡发送。
两个回执接收器各司其职:
- SmsSentReceiver:
resultCode == RESULT_OK则把状态改为SENT,否则标记ERROR(界面上会显示重发按钮); - SmsDeliveredReceiver:收到对方网络的回执后把状态更新为
DELIVERED,即消息气泡变成"双勾"。
此外,SmsUtil.kt#L117-L150 还实现了短信长度规则:纯 ASCII 每段 160 字符,含 Unicode(如中文表情)每段 70 字符,超长文本自动拆分成多条发送。
HeadlessSmsSendService:源码只有 7 行,为什么重要?
打开 HeadlessSmsSendService,你会发现它几乎是个空类——onBind直接返回 null。这正是 Google 官方推荐的"无头短信发送服务"写法:系统检测到应用声明了这个服务后,就把它认定为一个短信类应用,从而:
- 获得进入系统"短信应用选择器"的资格,可被设为主短信应用;
- 支持通话中的"快速回复"(Quick Reply)场景——系统通过
RESPOND_VIA_MESSAGE动作唤起该服务代为发送短信。
它的注册在 AndroidManifest.xml#L282-L296:声明android.permission.SEND_RESPOND_VIA_MESSAGE权限保护,并监听sms、smsto、mms等 scheme 的 intent-filter。真正"干活"的发送逻辑仍由SmsManager完成,服务本身只是一个身份声明。
AndroidManifest 中的权限与组件注册清单
离线收发的能力,一半写在了 AndroidManifest.xml 里,主要有三处:
- 三大短信权限(第 20-23 行):
READ_SMS(读历史短信)、RECEIVE_SMS(接收短信广播)、SEND_SMS(发送短信); - 高优先级短信接收器(第 239-247 行):
SmsReceiver使用android.permission.BROADCAST_SMS系统保护权限,且 intent-filter 设置priority="999",保证比系统短信应用更早收到广播; - 隐私关键一行(第 43-45 行):
tools:node="remove"移除了依赖库引入的ACCESS_NETWORK_STATE网络权限——配合"移除网络权限"的思路,从底层证明该应用短信功能零联网。
本地存储还是系统短信库?一键切换
Connect You 的仓库层做了抽象:应用启动时,App.kt#L35-L41 的initSmsRepo()会根据"本地存储短信"设置,决定使用LocalSmsRepo(内置 Room 数据库,完全脱离系统短信库)或默认的 DeviceSmsRepo。
DeviceSmsRepo通过系统ContentProvider(Telephony.Sms.CONTENT_URI)直接读写 Android 内置短信数据库,并用ContentObserver监听数据变化、以Flow推送给界面——所以在 Connect You 里发送的短信,系统短信应用同样能看到,真正做到"本地即系统"。
总结
Connect You 的短信模块是一套教科书级的"纯本地短信收发"实现:
- 📥收:
SmsReceiver以 999 优先级拦截SMS_DELIVER广播 → 系统短信库 → 可回复/删除/复制验证码的通知; - 📤发:
SmsUtil先入库再交给SmsManager,两个回执 Receiver 跟踪"已发送 / 已送达"状态; - 🧩资格声明:7 行代码的
HeadlessSmsSendService让应用获得系统短信应用身份与快速回复能力; - 🔒离线保障:移除网络权限、零服务器依赖,短信数据只存在你自己的设备里。
理解了"广播接收器负责进出、回执接收器负责状态、服务负责身份"这三个角色,你就能读懂绝大多数 Android 短信类应用的源码结构了。
【免费下载链接】ConnectYouPrivacy focused contacts and SMS messenger app built with MD3项目地址: https://gitcode.com/gh_mirrors/co/ConnectYou
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考