Android闹钟管理器 - RTC_WAKEUP vs ELAPSED_REALTIME_WAKEUP

91

有人能解释一下AlarmManager.RTC_WAKEUPAlarmManager.ELAPSED_REALTIME_WAKEUP之间的区别吗?我已经阅读了文档,但仍然不太理解在使用一个而非另一个时的影响。

示例代码:

    alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 
                     scheduledAlarmTime, 
                     pendingIntent);

    alarmManager.set(AlarmManager.RTC_WAKEUP, 
                     scheduledAlarmTime, 
                     pendingIntent);

这两行代码的执行有何不同?这两行代码相对于彼此执行的时间是什么时候?

感谢您的帮助。

5个回答

153

AlarmManager.ELAPSED_REALTIME_WAKEUP 类型用于自启动以来经过的时间来触发闹钟:

alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 600000, pendingIntent);

这会让闹钟在设备启动10分钟后响起。

设备启动后会有一个定时器开始运行,用来测量设备的正常运行时间,当设备运行时间达到预设时间时就会触发闹钟。

AlarmManager.RTC_WAKEUP则根据设备时钟的时间来触发闹钟。例如,如果您执行以下操作:

long thirtySecondsFromNow = System.currentTimeMillis() + 30 * 1000;
alarmManager.set(AlarmManager.RTC_WAKEUP, thirtySecondsFromNow , pendingIntent);

另一方面,这将在30秒后触发警报。

AlarmManager.ELAPSED_REALTIME_WAKEUP类型相对于AlarmManager.RTC_WAKEUP很少使用。


1
这正是我想的。我只需要一些确认。所以如果我做了这样的事情:alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, System.currentTimeMills() + 30 * 1000, pendingIntent); 可能会发生奇怪的事情(这就是我之前遇到的问题,直到我注意到它并不像我想的那样工作)。 - Camille Sévigny
2
请注意,代码应该是 System.currentTimeMillis() 而不是 System.currentTimeMills() :) - HasanAboShally
17
AlarmManager.ELAPSED_REALTIME_WAKEUP类型相对于AlarmManager.RTC_WAKEUP很少使用。这是一种猜测和错误的建议。根据文档:https://developer.android.com/training/scheduling/alarms.html"如果您只需要让您的闹钟在特定间隔(例如每半小时)触发,请使用其中一个经过的实时类型。一般来说,这是更好的选择。" - Jared Kells
1
@mborsuk的答案更好且更正了这个答案:你不应该使用RTC来衡量经过的时间,比如“从现在开始三十秒”。 - Micha F.
2
很好的解释 :)! 我想补充一个重要的评论:根据Android的官方文件,当涉及到触发向服务器发出HTTP请求的应用程序,并且您不想在web服务器上生成任何负载时,使用“AlarmManager.ELAPSED_REALTIME_WAKEUP”可能会有所帮助。 想象一下成千上万的Android设备,在晚上10:30执行GET请求的情况:由于其工作超过了设备的启动时间,“AlarmManager.ELAPSED_REALTIME_WAKEUP”可以使这些“成千上万”的设备分别触发请求,而避免同时产生负载 :). - ivanleoncz

116

尽管当前被认可且获得赞同的答案是如此,但AlarmManager.ELAPSED_REALTIME*类型及SystemClock.elapsedRealtime()始终比RTC时钟更可靠,适用于闹钟及计时。

使用AlarmManager的ELAPSED_REALTIME_WAKEUP将依靠从启动时间开始的单调时钟"并且即使CPU处于节能模式下,该时钟仍会继续运行,因此是通用间隔定时的推荐基础"。因此,

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

如果您设置了PendingIntent的时间为1分钟(60*1000毫秒),它将在1分钟后触发。

而AlarmManager.RTC_WAKEUP是以自纪元以来的毫秒数表示的标准“墙壁”时间。因此,

alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
                 + 60*10000, pendingIntent);

根据SystemClock文档所述,可能还会在60秒后触发警报,但不可靠,因为:

壁钟可以由用户或手机网络设置(请参见setCurrentTimeMillis(long)),因此时间可能会不可预测地向前或向后跳变。只有在与现实日期和时间的对应关系很重要时,例如在日历或闹钟应用程序中,才应使用此时钟。间隔或经过时间测量应使用其他时钟。如果您正在使用System.currentTimeMillis(),请考虑听取ACTION_TIME_TICK、ACTION_TIME_CHANGED和ACTION_TIMEZONE_CHANGED Intent广播,以了解时间何时更改。

此外,该问题仅涉及*_WAKEUP警报,但请参阅AlarmManager文档,以确保您了解唤醒警报与非唤醒警报提供的内容。


你的回答很好,但在我的应用程序中,我需要设置与现实世界日期/时间相符的闹钟,而不仅仅是从现在开始的60秒。在这种情况下,RTC_WAKEUP对我来说是最好的解决方案。 - Camille Sévigny
9
好的,但这是对问题更准确的回答,并纠正了目前被接受答案中的错误。 - mborsuk
+1 对这个信息表示赞赏,但请注意 elapsedRealtime() 方法属于 SystemClock 而不是 System。编辑:...已达到每日投票限制... - Jorge Fuentes González
这是问题中最好的答案之一。我在寻找一个50米高的干草堆中的针(也就是“我的代码中的一个错误!”),而你的答案直接把我带到了针的位置! - AlxDroidDev

17

仅供参考。你可以调用以下代码来获取系统运行时间(以毫秒为单位):

long uptimeMillis =  SystemClock.elapsedRealtime();

所以,如果你想在30秒后触发闹钟,并且想使用uptime时钟而不是普通时钟,可以这样做:

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

每当您想要检查经过的时间而不是特定的日期/时间时,最好使用系统运行时间。这是因为用户在设备上设置的当前时间如果被用户使用设置更改了,则可能会发生变化。


3
感谢您指出“每当您想检查经过的时间”这一点,顿时让人明白了。 - sulai
再次强调一下:我的理解是,除非发生像设备关闭这样的情况,否则它将在30秒后触发。具体来说,我认为使用RTC_WAKEUP的问题在于,理论上,15秒后,可能会变成10月底某个星期六的凌晨1点(至少在美国和欧洲大部分地区),因此闹钟实际上要在设置后1小时30秒才会触发。 - Yannick

2

在选择要使用的闹钟时,有一些重要的注意事项(适用于已经阅读了投票结果的人):

RTC_WAKEUP 时钟更改问题: RTC_WAKEUP 闹钟不会响铃,如果用户手动将时间更改为过去,并且如果它超过了 RTC 时间戳,则会立即响铃。请不要使用此闹钟进行客户端验证或重要工作,因为它可能会失败。

WAKEUP 意义(适用于 Marshmallow 及以上版本)
总的来说,没有太大意义。无法在设备处于空闲状态或进入 doze 模式时唤醒设备,需要使用 alarmManager.setExactAndAllowWhileIdlealarmManager.setAndAllowWhileIdleDoze & Idle)。


2
我在自己的项目中以这种方式解决了这个问题。在下面的代码中,我使用
AlarmManager.ELAPSED_REALTIME_WAKEUP

如何在特定时间设置闹钟。

变量'intentName'用于intentFilter以接收此类闹钟,因为我正在触发许多此类闹钟。当我取消所有闹钟时,我使用底部给出的cancel方法来取消。

//用于保留闹钟并在需要时取消

     public static ArrayList<String> alarmIntens = new ArrayList<String>();

//

    public static String setAlarm(int hour, int minutes, long repeatInterval,
        final Context c) {
    /*
     * to use elapsed realTime monotonic clock, and fire alarm at a specific time
     * we need to know the span between current time and the time of alarm.
     * then we can add this span to 'elapsedRealTime' to fire the alarm at that time
     * this way we can get alarms even when device is in sleep mood
    */
    Time nowTime = new Time();
    nowTime.setToNow();
    Time startTime = new Time(nowTime);
    startTime.hour = hour;
    startTime.minute = minutes;
    //get the span from current time to alarm time 'startTime'
    long spanToStart = TimeUtils.spanInMillis(nowTime, startTime);
    //
    intentName = "AlarmBroadcast_" + nowTime.toString();
    Intent intent = new Intent(intentName);
    alarmIntens.add(intentName);
    PendingIntent pi = PendingIntent.getBroadcast(c, alarms++, intent,
            PendingIntent.FLAG_UPDATE_CURRENT);
    //
    AlarmManager am = (AlarmManager) c
            .getSystemService(Context.ALARM_SERVICE);
    //adding span to elapsedRealTime
    long elapsedRealTime = SystemClock.elapsedRealtime();
    Time t1 = new Time();
    t1.set(elapsedRealTime);
    t1.second=0;//cut inexact timings, seconds etc
    elapsedRealTime = t1.toMillis(true);

    if (!(repeatInterval == -1))
        am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                elapsedRealTime + spanToStart, repeatInterval, pi);
    else
        am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapsedRealTime
                + spanToStart, pi);
函数是什么意思:
 public static long spanInMillis(Time startTime, Time endTime) {
    long diff = endTime.toMillis(true) - startTime.toMillis(true);
    if (diff >= 0)
        return diff;
    else
        return AlarmManager.INTERVAL_DAY - Math.abs(diff);
}

取消警报功能就是这样。
public static void cancel(Context c) {
    AlarmManager am = (AlarmManager) c
            .getSystemService(Context.ALARM_SERVICE);
    // cancel all alarms
    for (Iterator<String> iterator = alarmIntens.iterator(); iterator
            .hasNext();) {
        String intentName = (String) iterator.next();
        // cancel
        Intent intent = new Intent(intentName);
        PendingIntent pi = PendingIntent.getBroadcast(c, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        am.cancel(pi);
        //
        iterator.remove();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接