11 KiB
Context
当前 app 已有短信验证码接收实现:Manifest 声明了 RECEIVE_SMS、READ_SMS,静态 SmsReceiver 监听 android.provider.Telephony.SMS_RECEIVED,MainActivity 提供权限申请、最近结果展示、READ_SMS 最新短信读取、ContentObserver 和短时轮询诊断。用户反馈“逻辑试过了能用”,但不确定为什么某些场景 onReceive 收不到。
目标设备是小米 12S、澎湃 OS 3、Android 15。根据 Android 官方文档,RECEIVE_SMS 允许应用接收 SMS,但它是 dangerous 且 hard restricted 权限,是否能真正持有可能受安装来源/安装器 allowlist 影响。Android 15 又新增了 BOOT_COMPLETED 启动部分前台服务类型的限制;Doze/App Standby 白名单也不是无限制后台执行。小米官方支持文档说明 HyperOS/小米系统存在“Background autostart”用户开关,路径为 Settings > Apps > Permissions > Background autostart。
参考资料:
- Android 15 前台服务类型变更:https://developer.android.com/about/versions/15/changes/foreground-service-types
- Android 15 行为变更:https://developer.android.com/about/versions/15/behavior-changes-15
- Doze/App Standby 与电池优化:https://developer.android.com/training/monitoring-device-state/doze-standby
RECEIVE_SMS权限:https://developer.android.com/reference/android/Manifest.permission#RECEIVE_SMS- 小米后台自启动设置:https://www.mi.com/global/support/faq/details/KA-507608/
Goals / Non-Goals
Goals:
- 先形成完整 spec,不直接写代码。
- 在当前可用短信接收 app 上补齐后台保活和开机恢复方案。
- 让用户能手动完成小米设置,并在 app 内看到哪些设置已完成、哪些只能人工确认。
- 让
onReceive收不到时有明确诊断:权限、安装来源、force-stop、系统广播、HyperOS 后台策略、短信是否进入收件箱。 - 后续实现保持可解释、可关闭、可验证。
Non-Goals:
- 不做无通知、不可感知、规避系统策略的隐藏保活。
- 不承诺杀进程、force-stop、清后台、系统级省电清理后仍 100% 存活。
- 不把 C++ fork/native daemon 当成可靠能力;Android 应用沙箱和系统进程管理不会因为 native 代码而失效。
- 不把 app 做成默认短信客户端。
- 不要求本轮编译。
API Strategy
1. 系统短信广播仍是主路径
短信进入设备时,主路径仍是 SMS_RECEIVED_ACTION。这是最直接的验证码捕获路径,但它依赖:
- 应用真正持有
RECEIVE_SMS。 - 应用未被用户 force-stop。
- 系统或厂商策略允许静态 receiver 在当前状态下被拉起。
- 短信确实由 Android Telephony 入库/分发,而不是被系统短信 app 或安全策略特殊处理。
现有 SmsReceiver 的 manifest 写法包含 android:permission="android.permission.BROADCAST_SMS",该写法用于限制只有持有系统广播权限的发送方能投递该 receiver;系统短信广播通常满足此条件。后续诊断要验证它不是问题根因,但不建议先移除。
2. 前台服务用于“可见后台运行”,不用于读取短信
新增 SmsKeepAliveService:
- app 前台点击“开启常驻保活”后调用
startForegroundService。 - 服务在 5 秒内调用
startForeground,展示低打扰常驻通知。 - 通知内容显示“短信监听运行中”、最近一次心跳、最近一次短信来源。
- 服务只做状态心跳、通知刷新、诊断状态保存。
- 短信接收仍由
SmsReceiver和收件箱兜底路径处理。
这样做的原因是前台服务可以提升后台进程可见性,但不能替代短信广播,也不能保证被厂商永不杀。它的价值是让系统和用户明确知道该 app 在后台运行,并给 HyperOS “省电无限制 + 自启动”一个更稳定的承载对象。
3. 开机自启动采用轻量 BootReceiver
新增 BootReceiver 监听:
android.intent.action.BOOT_COMPLETEDandroid.intent.action.LOCKED_BOOT_COMPLETEDandroid.intent.action.MY_PACKAGE_REPLACED
收到后:
- 记录 boot 事件和时间。
- 检查用户是否曾开启“常驻保活”。
- 如果已开启,则尝试启动
SmsKeepAliveService。 - 捕获
ForegroundServiceStartNotAllowedException等异常,写入诊断,不崩溃。
Android 15 对 BOOT_COMPLETED 启动前台服务的限制集中在 dataSync、camera、mediaPlayback、phoneCall、mediaProjection、microphone 等类型。当前保活服务不应声明这些类型;如果后续升级 targetSdk 到 35,仍必须真机验证 BootReceiver 启动服务是否被 HyperOS 额外限制。
4. 电池优化与 HyperOS 设置采用“检测 + 引导”
Android 标准能力:
- 用
PowerManager.isIgnoringBatteryOptimizations(packageName)展示是否在电池优化白名单。 - 提供
Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS打开白名单设置。 - 个人自用场景可考虑
ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS直接请求,但 UI 必须说明它仍不等于无限后台。
小米/HyperOS 能力:
- 提供“打开小米自启动设置”按钮,优先尝试常见 MIUI/HyperOS 安全中心组件。
- 如果显式组件不可用,回退
ACTION_APPLICATION_DETAILS_SETTINGS和通用系统设置。 - UI 显示人工清单:
- 权限:短信权限已授权。
- 自启动:用户已在 HyperOS 开启。
- 省电策略:用户已设为无限制。
- 通知:常驻通知未被关闭。
- 保活服务:正在运行。
自启动和省电无限制通常没有稳定公开 API 可直接读取真实状态,因此必须允许用户手动勾选“我已完成设置”,并把这部分标记为人工确认。
5. Native/C++ 方案只做实验诊断
可以加入一个可选 native heartbeat:
- Java 服务加载 native library。
- native 层定期写入一个本地 heartbeat 文件或通过 JNI 回调返回时间戳。
- 用于确认进程活着、JNI 正常、服务被杀后心跳停止。
不建议实现 native fork daemon:
- Android 应用进程被系统管理,子进程同属应用 UID/cgroup,厂商清理通常会一起处理。
- 后台私自 fork 常驻进程会带来电量、兼容性和安全风险。
- 对短信广播恢复没有直接帮助。
如果用户坚持尝试,必须放在“实验开关”下,并以诊断结论交付,不作为保活主链路。
Why onReceive May Not Fire
后续 UI 和 logcat 诊断必须覆盖以下根因:
RECEIVE_SMS未授权,或因 hard restricted 机制安装后实际不可持有。- app 被 force-stop;Android 不会为 force-stopped app 投递大多数隐式广播,直到用户再次打开。
- 小米自启动未开启,系统不允许后台拉起静态 receiver 或服务。
- 省电策略不是无限制,后台/锁屏/待机时进程或广播处理被延迟/限制。
- app 刚安装后从未启动,部分系统不会让 receiver 正常进入用户期望状态。
- 短信由运营商/RCS/系统短信能力特殊处理,未走普通 SMS_RECEIVED。
- 双卡或短信格式异常导致 PDU 解析失败,但此时 receiver 应该已有日志。
- Manifest receiver 被禁用、包名不匹配、构建安装的不是当前调试版本。
- 通知权限关闭不应直接阻止短信广播,但可能影响前台服务可见性和用户判断。
诊断顺序建议:
- 看 UI 权限状态和最近 boot/service 心跳。
- 发送短信时抓 logcat
SmsReceive。 - 如果 receiver 无日志,点“读取最新短信”确认短信是否入库。
- 如果短信已入库但 receiver 无日志,重点排查权限、force-stop、HyperOS 自启动和省电。
- 如果 receiver 有日志但无验证码,排查 PDU/body/parser。
Data Model
新增 KeepAliveState 本地状态:
enabledByUser: 用户是否开启常驻保活。serviceRunning: 最近一次服务 onCreate/onStartCommand/onDestroy 状态。lastHeartbeatMillis: 服务最近心跳。lastBootEvent: 最近一次 boot/package replaced 事件。lastServiceStartFailure: 最近一次服务启动失败原因。batteryOptimizationIgnored: 标准 Android 电池优化白名单状态。manualAutostartConfirmed: 用户手动确认已开启小米自启动。manualBatteryUnrestrictedConfirmed: 用户手动确认已设置省电无限制。notificationEnabledHint: 通知是否可能可见。
保存方式优先使用 SharedPreferences,与现有 SmsCaptureStore 保持简单一致。
UI Strategy
首版继续使用现有 Java View UI,不引入新框架:
- 增加“后台保活状态”区域:
- 保活开关。
- 服务运行状态。
- 最近心跳。
- 最近开机事件。
- 最近启动失败原因。
- 增加“系统设置”区域:
- 打开应用详情。
- 打开电池优化设置。
- 请求忽略电池优化。
- 打开小米自启动设置。
- 人工确认自启动已开启。
- 人工确认省电无限制已开启。
- 增加“短信广播诊断”区域:
- 最近
system_sms_broadcast时间。 - 最近
sms_inbox_*时间。 - 当收件箱有新短信但广播未到时提示“疑似广播未投递”。
- 最近
Test Strategy
单元/本地测试:
KeepAliveStateStore读写测试。BootReceiver在不同 action 下生成正确状态。SmsKeepAliveService的状态更新逻辑抽出为纯 Java 方法测试。- 设置 intent builder 测试:小米组件不可用时能 fallback。
ADB/真机验证:
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n com.smsreceive.app/.BootReceiver- 重启手机后验证保活服务是否恢复。
- 开启保活通知后,后台 30 分钟、锁屏 30 分钟分别发送短信。
- 开启/关闭小米自启动、开启/关闭省电无限制,对比短信广播和收件箱兜底结果。
- 手动 force-stop 后发送短信,确认不承诺接收,并在再次打开 app 后展示诊断。
- Android Doze 测试可参考官方命令
dumpsys deviceidle force-idle、am set-inactive。
Rollout Plan
- 完成本 OpenSpec 并 validate。
- 先实现 Java 层 BootReceiver、Foreground Service、通知和状态 UI。
- 再补小米设置入口和人工确认状态。
- 最后根据真机结果决定是否加入 native heartbeat 实验。
- 如果前台服务/开机恢复在 HyperOS 上仍不稳定,交付时明确记录限制和必须手动设置项。
Open Questions
- 当前安装方式是 Android Studio/adb 直装,还是包管理器/文件管理器安装 APK;这会影响 hard restricted SMS 权限表现。
- 用户是否接受常驻通知一直显示。
- 是否希望 app 开机后自动开启保活,还是必须用户先在 UI 中开启一次后才持久化。
- 是否需要 native heartbeat 实验;建议第一轮 Java 方案真机验证后再决定。