204 lines
11 KiB
Markdown
204 lines
11 KiB
Markdown
## 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_COMPLETED`
|
||
- `android.intent.action.LOCKED_BOOT_COMPLETED`
|
||
- `android.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 被禁用、包名不匹配、构建安装的不是当前调试版本。
|
||
- 通知权限关闭不应直接阻止短信广播,但可能影响前台服务可见性和用户判断。
|
||
|
||
诊断顺序建议:
|
||
|
||
1. 看 UI 权限状态和最近 boot/service 心跳。
|
||
2. 发送短信时抓 logcat `SmsReceive`。
|
||
3. 如果 receiver 无日志,点“读取最新短信”确认短信是否入库。
|
||
4. 如果短信已入库但 receiver 无日志,重点排查权限、force-stop、HyperOS 自启动和省电。
|
||
5. 如果 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
|
||
|
||
1. 完成本 OpenSpec 并 validate。
|
||
2. 先实现 Java 层 BootReceiver、Foreground Service、通知和状态 UI。
|
||
3. 再补小米设置入口和人工确认状态。
|
||
4. 最后根据真机结果决定是否加入 native heartbeat 实验。
|
||
5. 如果前台服务/开机恢复在 HyperOS 上仍不稳定,交付时明确记录限制和必须手动设置项。
|
||
|
||
## Open Questions
|
||
|
||
- 当前安装方式是 Android Studio/adb 直装,还是包管理器/文件管理器安装 APK;这会影响 hard restricted SMS 权限表现。
|
||
- 用户是否接受常驻通知一直显示。
|
||
- 是否希望 app 开机后自动开启保活,还是必须用户先在 UI 中开启一次后才持久化。
|
||
- 是否需要 native heartbeat 实验;建议第一轮 Java 方案真机验证后再决定。
|