Android AlarmManager: Setup Repeating Alarm

May 9, 2019

This example is suited for repeating alarm at certain interval (e.g. hourly, daily, etc) but doesn’t gurantee accuracy of repeating time (e.g. 9am daily). Refer to Caveats of Repeating Alarm.

class ReminderReceiver : BroadcastReceiver() {
    companion object {
        private const val REQUEST_TIMER1 = 1

        fun getIntent(context: Context, requestCode: Int): PendingIntent? {
            val intent = Intent(context, ReminderReceiver::class.java)
            // https://developer.android.com/reference/android/app/PendingIntent.html#getBroadcast(android.content.Context,%20int,%20android.content.Intent,%20int)
            return PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT)
        }

        fun startAlarm(context: Context) {
            val pendingIntent = getIntent(context, REQUEST_TIMER1)
            val alarm = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

            /*
            // trigger at 8:30am
            val alarmTime = LocalTime.of(8, 30)
            var now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES)
            if (now.toLocalTime().isAfter(alarmTime)) {
                now = now.plusDays(1)
            }
            now = now.withHour(alarmTime.hour).withMinute(alarmTime.minute) // .withSecond(alarmTime.second).withNano(alarmTime.nano)
            val utc= now.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()

            val triggerAtMillis = utc.atZone(ZoneOffset.UTC)!!.toInstant()!!.toEpochMilli()
            // first trigger at next 8:30am, then repeat each day
            alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, AlarmManager.INTERVAL_DAY, pendingIntent)
             */

            // this alarm might execute between now to next day, and repeat daily
            alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), AlarmManager.INTERVAL_DAY, pendingIntent)
        }

        fun cancelAlarm(context: Context) {
            val pendingIntent = getIntent(context, REQUEST_TIMER1)
            val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
            alarmManager.cancel(pendingIntent)
        }
    }

    override fun onReceive(context: Context, intent: Intent) {
        Timber.d("onReceive")
        Toast.makeText(context, "Alarm!", Toast.LENGTH_LONG).show()
    }
}

Usage

ReminderReceiver.startAlarm(context)

NOTES:

  • PendingIntent.FLAG_CANCEL_CURRENT to make sure only 1 alarm is created/overwritten no matter how many times startAlarm is called
  • setInexactRepeating: The alarm’s first trigger will not be before the requested time, but it might not occur for almost a full interval after that time. In addition, while the overall period of the repeating alarm will be as requested, the time between any two successive firings of the alarm may vary.
  • triggerAtMillis indicate when is the first time it should triggered. If the time has passed, it might trigger immediately.
  • triggerAtMillis is in GMT/UTC

Caveats of Repeating Alarm

setRepeating

If an alarm is delayed (by system sleep, for example, for non _WAKEUP alarm types), a skipped repeat will be delivered as soon as possible. After that, future alarms will be delivered according to the original schedule; they do not drift over time. For example, if you have set a recurring alarm for the top of every hour but the phone was asleep from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens, then the next alarm will be sent at 9:00.

As of API 19, all repeating alarms are inexact. Because this method has been available since API 3, your application can safely call it and be assured that it will get similar behavior on both current and older versions of Android.

setInexactRepeating

Your alarm’s first trigger will not be before the requested time, but it might not occur for almost a full interval after that time. In addition, while the overall period of the repeating alarm will be as requested, the time between any two successive firings of the alarm may vary. If your application demands very low jitter, use one-shot alarms with an appropriate window instead; see setWindow(int, long, long, android.app.PendingIntent) and setExact(int, long, android.app.PendingIntent).

It seems as of API 19 (Android 4.4 - KitKat), calling setRepeating is same as setInexactRepeating. It is suitable for job that repeats within a certain interval (hourly, daily, etc.), but doesn’t need to execute at the exact time (e.g. 9am every day). I believe the repeating alarm will drift (as setRepeating behaviour is inexact as of API 19), as in the following sequence might occur

  • Day 1: 9am
  • Day 2: 9.05am
  • Day 3: 9.30am
  • Day 4: 10am
  • Day 5: 10am
  • Day 6: 11.30am

If you need to repeat at specific time daily (e.g. 9am every day), use setExact or setWindow (allow slight delay as per the window specified, but more energy efficient). If you need extreme accuracy (e.g. Alarm Clock) and execute even though the system is in low-power idle modes, use setExactAndAllowWhileIdle.

You have to program the repetition part yourself, set the next alarm again after the current alarm is triggered at onReceive.

Refer to Daily Repeating Alarm/Reminder at Specific Time With AlarmManager.

Set Alarm Timing Example

Example: trigger once immediately.

val triggerAtMillis = System.currentTimeMillis()
alarm.set(AlarmManager.RTC_WAKEUP,  triggerAtMillis, pendingIntent)

Example: trigger once 5 minutes later.

val intervalMillis = 5L * 60L * 1_000L
val triggerAtMillis = System.currentTimeMillis() + intervalMillis
alarm.set(AlarmManager.RTC_WAKEUP,  triggerAtMillis, pendingIntent)

Example: trigger once every 5 minutes

val intervalMillis = 5L * 60L * 1_000L
val triggerAtMillis = System.currentTimeMillis() + intervalMillis
alarm.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, intervalMillis, pendingIntent)

AndroidManifest.xml

Edit AndroidManifest.xml.

```xml
<manifest ..>
    <application ...>
        <receiver android:name=".ReminderReceiver" />
    </application>
</manifest>

NOTE: The AlarmManager won’t survive a device reboot. You have to implement a device bootup receiver and call ReminderReceiver.startAlarm(context).

NOTE: Refer Setup Android Notification.

NOTE: To support multiple alarm, use a different requestCode for each alarm. Refer Android AlarmManager: Multiple Alarm With Arguments/Parameters.

NOTE: AlarmManager is removed when the app is uninstalled, and it seems the Alarm is cancelled after APK update (not sure if this is true or always true).

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.