11、AlarmManager:AlarmManager详解、set() vs setExact() vs setAlarmClock()、省电注意事项

AlarmManager,说白了就是Android系统里的一个“闹钟服务”。

它允许你在指定的时间点,或者经过一段延迟后,执行某个任务。哪怕你的App被杀了,手机在睡觉,它也能把你叫醒。

嗯,这里要注意——它叫“闹钟”,但跟床头那个闹钟不完全一样。它更准确的说法是“定时任务调度器”。

核心一句话:AlarmManager 是 Android 系统级定时器,用于在指定时间触发 PendingIntent。

11.1 AlarmManager 的工作原理

我个人习惯把 AlarmManager 理解成一个“快递员”。

你给它一个包裹(PendingIntent),告诉它几点几分送到。到了时间,它就把包裹扔到你的App门口(触发广播或启动服务)。

但这里有个坑——Android 系统为了省电,会把多个闹钟合并在一起触发。这就是“闹钟对齐”机制。

举个例子:

  • 你设了一个10:05的闹钟
  • 另一个App设了一个10:07的闹钟
  • 系统可能把两个闹钟都拖到10:10一起触发

为什么会这样?因为频繁唤醒CPU太费电了。系统宁愿多睡一会儿,一次性处理完。

我的经验:我在做一款运动健康App时,需要每15分钟记录一次心率。一开始用set(),结果发现记录时间总是漂移。后来才发现是闹钟对齐搞的鬼。

11.2 set() vs setExact() vs setAlarmClock()

这三个方法,是AlarmManager里最常用的。但它们的区别,很多人搞不清楚。

我直接给你一张对比表:

方法 精度 省电 唤醒设备 典型场景
set() 低(允许对齐) 最省电 定时刷新、非关键任务
setExact() 高(精确触发) 较费电 倒计时、限时任务
setAlarmClock() 最高(精确+显示图标) 最费电 真正的闹钟、提醒

11.2.1 set() —— 省电优先

这是最常用的方法。系统会把你的闹钟跟其他闹钟合并,一起触发。

你想想看,如果你只是每隔一小时刷新一下天气数据,完全没必要精确到秒。用set()就够了。

// 设置一个30分钟后的非精确闹钟
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, MyReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);

alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 
                 SystemClock.elapsedRealtime() + 30 * 60 * 1000, 
                 pendingIntent);

注意:从Android 12(API 31)开始,set()的触发延迟可能高达10分钟。别指望它准时。

11.2.2 setExact() —— 精确但费电

当你需要“差不多准时”时,用setExact()。

它不会跟其他闹钟对齐,但系统仍然可能因为Doze模式而延迟触发。

我曾经在做一个“抢购倒计时”功能时,用了setExact()。结果发现,在Doze模式下,倒计时结束后的弹窗晚了整整2分钟才出现。用户都骂疯了。

// 设置一个精确的5秒后闹钟
alarmManager.setExact(AlarmManager.RTC_WAKEUP, 
                      System.currentTimeMillis() + 5000, 
                      pendingIntent);

避坑指南:setExact()在Android 6.0以上,如果手机处于Doze模式,仍然会被延迟。要绕过Doze,请使用setExactAndAllowWhileIdle()。

11.2.3 setAlarmClock() —— 真正的闹钟

这个方法,是唯一一个“保证准时”的。

系统会为它做两件事:

  • 强制退出Doze模式
  • 在状态栏显示一个闹钟图标

说白了,这就是给“用户明确期望”的闹钟用的。比如早上7点的起床闹钟。

// 设置一个真正的闹钟
AlarmManager.AlarmClockInfo alarmClockInfo = 
    new AlarmManager.AlarmClockInfo(System.currentTimeMillis() + 10000, pendingIntent);
alarmManager.setAlarmClock(alarmClockInfo, pendingIntent);

重要:setAlarmClock()会显示闹钟图标,用户一看就知道你App设了闹钟。别滥用,否则用户会卸载你。

11.3 省电注意事项

AlarmManager用不好,就是电老虎。我见过太多App因为滥用AlarmManager,被用户投诉“耗电快”。

这里我总结几条铁律:

  1. 能用set()就别用setExact() —— 非关键任务,让系统帮你对齐。
  2. 能用setExact()就别用setAlarmClock() —— 除非你真的需要闹钟图标。
  3. 减少唤醒频率 —— 能5分钟一次就别1分钟一次。电池经不起折腾。
  4. 使用批量闹钟 —— 如果你有多个定时任务,尽量合并成一个PendingIntent。
  5. 取消不再需要的闹钟 —— 用alarmManager.cancel(pendingIntent)及时清理。

我的血泪教训:曾经有一个版本,我在App里设了20多个闹钟,分别用于不同功能。结果用户反馈手机待机时间直接砍半。后来我全部改成set(),并合并成3个闹钟,耗电问题才解决。

11.4 知识体系图

下面这张图,帮你理清AlarmManager的核心逻辑:

AlarmManager 核心知识体系 AlarmManager set() 非精确 · 省电 · 可对齐 setExact() 精确 · 较费电 · 可被Doze延迟 setAlarmClock() 最精确 · 最费电 · 显示图标 省电注意事项 ① 优先使用 set(),避免频繁唤醒 ② 合并多个闹钟,减少PendingIntent数量 ③ 及时 cancel() 不再需要的闹钟 ④ Doze模式下使用 setAndAllowWhileIdle()

11.5 实战建议

最后,给你几个我在项目中实际用到的建议:

  • 定时刷新数据:用set() + JobScheduler 配合,别只用AlarmManager。
  • 倒计时功能:用setExact(),但记得在onReceive里重新设置下一次。
  • 用户主动设置的提醒:用setAlarmClock(),这是唯一让用户信任的方式。
  • 测试时:用adb shell dumpsys alarm查看所有闹钟列表,排查问题。

我的习惯:每次发布前,我都会用dumpsys检查一遍App注册的闹钟数量。超过5个,我就开始优化。毕竟,用户手机的电池,比你的KPI重要得多。


公众号:蓝海资料掘金营,微信deep3321