1 分钟阅读 次阅读

概要:本文是关于 Android AlarmManager 的使用笔记,记录了精确与不精确闹钟的区别、权限配置、重复闹钟的处理方式,以及如何在系统重启后保持闹钟有效。适用于需要在应用外执行定时任务的场景。

1. 参考链接

https://developer.android.com/develop/background-work/services/alarms?hl=zh-cn


2. 闹钟功能概述

  • AlarmManager 用来在应用生命周期之外定时执行任务。
  • 举例:每天定时启动服务下载天气。
  • 特性总结如下:

    • 可根据设定时间/间隔触发 Intent。
    • 通常配合广播接收器使用,执行 Job/WorkRequest 等任务。
    • 在应用关闭甚至设备休眠时也能触发。
    • 避免使用持续服务或计时器,节省资源。

3. 闹钟类型

分为两类:

  • 不精确闹钟(节能/低频需求)
  • 精确闹钟(对时效性要求高)

4. 不精确闹钟

常见方法:

  • set()
  • setInexactRepeating()
  • setAndAllowWhileIdle()

注:这些方法不会早于设定时间触发。

Android 12+ 的行为:

  • 如果没有省电限制,系统会在设定时间后“1 小时内”触发。
  • setWindow() 支持设定触发窗口,但也不会早于指定时间。
  • windowLengthMillis 最低被限制为 600000(10 分钟),也就是说触发时间范围最小也是 10 分钟。

5. 精确闹钟

适合执行对时间敏感的任务。以下是常用方法(按资源消耗从小到大排列):

  • setExact(): 基本精确,不保证在省电模式下执行
  • setExactAndAllowWhileIdle(): 即使省电模式也会执行
  • setAlarmClock(): 最关键的闹钟类型,用户可见性强,系统不会延迟

6. 权限配置

https://developer.android.com/develop/background-work/services/alarms?hl=zh-cn#exact-permission-declare

6.1 Android 12+ 权限声明

必须在 manifest 中添加:

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>

6.2 Android 13+ 可以选择:

<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>

6.3 权限对比

权限项 特点说明
USE_EXACT_ALARM 自动授予、不可撤销、场景受限
SCHEDULE_EXACT_ALARM 用户手动授予、适用更广泛场景
  • 建议只在“确实需要精确计时”的场景使用上述权限。
  • 应确认是否被用户撤销。

7. 重复闹钟注意事项

  • Android 4.4+ 所有重复闹钟都被强制为“不精确”。
  • 设备关机后闹钟默认失效 → 需要监听 ACTION_BOOT_COMPLETED 重设闹钟。
  • 为避免服务器压力,建议:

    • 加入触发时间“抖动”
    • 优先执行本地任务(不依赖网络)
    • 网络请求类任务尽量随机分布时间

8. 示例代码

8.1 设置精确闹钟

Intent sleepIntent = new Intent(ctx, SleepReceiver.class);
PendingIntent sleepPi = PendingIntent.getBroadcast(ctx, 101, sleepIntent, PendingIntent.FLAG_IMMUTABLE);
am.cancel(sleepPi); // 先取消已有闹钟

long trigger = nextTriggerMillis(sh, sm, now); // 计算下一次触发时间
if (Build.VERSION.SDK_INT >= 23) {
    am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, trigger, sleepPi);
} else {
    am.setExact(AlarmManager.RTC_WAKEUP, trigger, sleepPi);
}

8.2 触发后重设闹钟(适用于重复场景)

public void onReceive(Context c, Intent i) {
    // 执行动作
    ...

    // 重设闹钟
    if (repeat) {
        long next = nextTriggerMillis(h, m, System.currentTimeMillis());
        AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
        Intent sleepIntent = new Intent(c, SleepReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(c, 101, sleepIntent, PendingIntent.FLAG_IMMUTABLE);
        if (Build.VERSION.SDK_INT >= 23)
            am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, next, pi);
        else
            am.setExact(AlarmManager.RTC_WAKEUP, next, pi);
    }
}

8.3 系统重启后自动重设闹钟

@Override
public void onReceive(Context c, Intent i) {
    if (!Intent.ACTION_BOOT_COMPLETED.equals(i.getAction())) return;

    AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
    long t = nextTriggerMillis(sh, sm, System.currentTimeMillis());
    Intent sleepIntent = new Intent(c, SleepReceiver.class);
    PendingIntent spi = PendingIntent.getBroadcast(c, 101, sleepIntent, PendingIntent.FLAG_IMMUTABLE);
    if (Build.VERSION.SDK_INT >= 23)
        am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, t, spi);
    else
        am.setExact(AlarmManager.RTC_WAKEUP, t, spi);
}

留下评论