2026-05-18 11:10:52 +08:00

204 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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-stopAndroid 不会为 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 方案真机验证后再决定。