应用关闭时接收广播(AlarmManager+显式 PendingIntent+静态 Receiver)
一、需求
写一个设置定时开关机的应用,需要实现:
- 应用已经被用户手动划掉
- 前台没有 Activity
- 但 到达设定时间后,
BroadcastReceiver仍然能被触发
代码如下:
private void scheduleSleep() {
int h = sp.getInt(KEY_SH, -1);
int m = sp.getInt(KEY_SM, -1);
if (h < 0 || m < 0) return;
long t = nextTriggerMillis(h, m, System.currentTimeMillis());
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(ctx, SleepReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(
ctx, 101, i, PendingIntent.FLAG_IMMUTABLE
);
am.cancel(pi);
if (Build.VERSION.SDK_INT >= 23)
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, t, pi);
else
am.setExact(AlarmManager.RTC_WAKEUP, t, pi);
}
问题随之而来:
- 这是不是系统广播?
- 为什么 App 被关闭还能收到?
- 和
android:sharedUserId="android.uid.system"有关系吗?
二、先澄清一个核心误区:什么叫“应用被关闭”?
这是理解问题的关键。
❌ 常见误解
应用被关闭 = 应用不能再执行任何代码
✅ Android 中的真实情况
在 Android 系统里:
-
“关闭应用”通常只是:
- Activity 被销毁
- 进程可能被回收
-
并不等于:
- App 被卸载
- App 的组件不能被系统拉起
📌 Android 是组件驱动模型,不是进程常驻模型
三、这是不是系统广播?
不是。
上述代码发送的是一个显式广播(Explicit Broadcast),即直接指定目标接收者的广播:
Intent i = new Intent(ctx, SleepReceiver.class);
特点:
| 类型 | 说明 |
|---|---|
| 广播类型 | 显式广播 |
| 接收对象 | 只指向 SleepReceiver |
| 是否对外 | 否 |
| 是否系统广播 | 否 |
系统广播通常是:
ACTION_BOOT_COMPLETEDACTION_TIME_TICKACTION_SCREEN_ON
四、真正的关键:AlarmManager + PendingIntent
1️⃣ AlarmManager 是系统级调度器
AlarmManager 并不是普通 API,它是:
- system_server 中的系统服务
- 用于在未来某个时间点触发行为
- 不依赖 App 是否在前台 / 是否存活
当你调用:
am.setExactAndAllowWhileIdle(..., pi);
你实际上是在告诉系统:
“到这个时间点,请你替我执行这个 PendingIntent。”
2️⃣ PendingIntent:系统持有的“执行令牌”
PendingIntent 的本质是:
一个由系统保存的、代表应用身份的执行授权
PendingIntent.getBroadcast(...)
关键特性:
- 即使 App 进程被杀
- 即使用户从最近任务划掉
- PendingIntent 依然有效
-
系统可以在需要时:
- 创建 App 进程
- 直接调用目标组件
📌 这正是“应用关闭仍能收到广播”的根本原因
五、为什么 BroadcastReceiver 能拉起应用?
BroadcastReceiver 是 Android 的特殊组件:
- 不需要进程常驻
- 不需要 Activity 存在
-
系统允许:
- 为了投递广播
- 临时拉起应用进程
流程如下:
Alarm 到期
↓
system_server 触发 PendingIntent
↓
AMS 创建 App 进程(如不存在)
↓
实例化 BroadcastReceiver
↓
回调 onReceive()
你并不是“偷偷在后台运行”, 而是系统主动调用你的一次性组件。
六、静态 Receiver 注册是前提条件
需要在 Manifest 中声明 SleepReceiver:
<receiver
android:name=".SleepReceiver"
android:exported="false" />
那么它具备以下特性:
- 无需 App 运行
- 无需动态注册
- 可被系统直接唤起
⚠️ 对比:
| 注册方式 | App 被杀后能否收到 |
|---|---|
| 静态注册(Manifest) | ✅ 可以 |
| 动态注册(registerReceiver) | ❌ 不行 |
七、setExactAndAllowWhileIdle() 的“强度”
am.setExactAndAllowWhileIdle(...)
这意味着:
-
即使在:
- Doze 模式
- 深度休眠
- 屏幕关闭
-
系统也会:
- 唤醒设备
- 执行 PendingIntent
📌 这是 Android 提供的最强后台定时机制之一。
八、和 android:sharedUserId="android.uid.system" 有关系吗?
结论:基本没关系
sharedUserId="android.uid.system" 的作用是:
- 与系统应用共享 Linux UID
- 获得 system 权限能力
但它不会影响:
| 行为 | 是否相关 |
|---|---|
| AlarmManager 是否触发 | ❌ |
| PendingIntent 是否生效 | ❌ |
| App 被杀后能否收广播 | ❌ |
| Receiver 是否被拉起 | ❌ |
📌 普通第三方 App,只要代码一致,行为完全相同
留下评论