The AlarmManagerService (AMS) is a core system service responsible for dispatching time-based events. Unlike typical Java timers or threaded sleep mechanisms, alarms managed by AMS can persist across application boundaries and process lifecycles. They rely heavily on kernel level interfaces like /dev/rtc (Real Time Clock) and alarmtimer in Linux to ensure devices can be woken up from sleep.
Alarm Types
Android supports several clock bases, which map to distinct constants in the AlarmManager class:
RTC(Real Time Clock): Based onSystem.currentTimeMillis(). The alarm triggers when the wall clock reaches the desired time. It does not wake the device from sleep.RTC_WAKEUP: LikeRTC, but triggers a hardware wakeup via the kernel'salarmtimersubsystem if the device is asleep.ELAPSED_REALTIME: Based onSystemClock.elapsedRealtime(), measuring time since boot. It continues ticking even when the device is asleep but will not wake the device to trigger.ELAPSED_REALTIME_WAKEUP: LikeELAPSED_REALTIME, but wakes up the CPU.
Using ELAPSED_REALTIME is generally preferred for interval based alarms since it is not affected by the user changing the system clock time.
Alarm Batching and Doze Alignment
Since Android 4.4 (KitKat), all alarms are inexact by default. AlarmManagerService actively batches alarms from different apps that are scheduled to trigger around the same time. This batching aligns wakeups, significantly reducing the number of times the device transitions out of idle states.
During Doze (Device Idle mode), introduced in Android 6.0, standard alarms are deferred. DeviceIdleController coordinates with AlarmManagerService to force batches into periodic maintenance windows.
If you check the internal state using dumpsys:
adb shell dumpsys alarm
You will see output detailing "Next alarm clock", "Pending alarm batches", and "Recent wakeups". Batches are represented by the Batch class internally, grouping Alarm objects that overlap in their when and maxWhen execution windows.
High Priority Alarms: setExactAndAllowWhileIdle
For applications requiring precise timing even during Doze (like a calendar notification or a medical reminder), Android provides setExactAndAllowWhileIdle().
When invoked, the alarm bypasses standard Doze deferral. However, to prevent abuse, AMS enforces strict quotas. The system tracks these quotas using AppStandbyController. If an app fires too many "allow while idle" alarms, subsequent alarms are delayed until the quota replenishes.
// Example framework-side snippet of quota enforcement logic in AlarmManagerService.java
if (isAllowWhileIdle(alarm)) {
final int quota = getQuotaForBucketLocked(bucket);
if (alarmsFired >= quota) {
// Defer until quota resets
alarm.when = getNextQuotaResetTimeLocked();
}
}
Alarm Delivery Flow
- Scheduling: An app calls
AlarmManager.set(). This makes a Binder call toAlarmManagerService. - Registration: AMS encapsulates the request in an
Alarmobject, adding it to the appropriateBatch. It calculates the next global wakeup time. - Kernel Handoff: AMS uses JNI (via
com_android_server_AlarmManagerService.cpp) to update the kernel via timerfd or the older/dev/alarminterface. - Hardware Wake: The kernel configures the RTC hardware. When the time hits, an interrupt fires. The kernel wakes the CPU and signals the timerfd.
- Dispatch: The native layer notifies the Java layer.
AlarmManagerService.DeliveryTrackeracquires a partial wakelock (*alarm*). - Broadcast: AMS dispatches the pending intent (or listener callback) to the target application. Once the app's
BroadcastReceiver.onReceive()completes, AMS releases the wakelock.