Введение
**«Поставьте точный будильник на Android 12+» — и кандидат, написавший setExact без разрешения, получает SecurityException. Вопрос про AlarmManager проверяет не знание API, а то, следили ли вы за изменениями платформы за последние годы.
AlarmManager — компонент, который позволяет выполнять операции по расписанию вне жизненного цикла приложения: даже если приложение не запущено и экран выключен, система доставит ваш Intent в нужный момент. По сути это «будильник» для вашего кода.
Вопросы про него не так часты, как про Activity или Service, но коварны: кандидат помнит set() из старого туториала и не знает ничего про Doze и разрешения. Разберём тему целиком: как устроена тревога, какие бывают типы, что изменилось в Android 12+ и как тревоги переживают перезагрузку.
Как устроена тревога
Механика AlarmManager проста: вы регистрируете PendingIntent, который система «выстрелит» в заданное время. Обычно это broadcast-ресивер, который получает событие и запускает работу — например, ставит задачу в WorkManager.
fun scheduleReminder(context: Context) {
val alarmManager = context.getSystemService(AlarmManager::class.java)
val intent = Intent(context, ReminderReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
REQUEST_CODE,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
alarmManager.set(
AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + 30 * 60 * 1000L,
pendingIntent
)
}Три вещи, которые важно понимать про устройство тревог:
- Тревога живёт вне приложения. Пока PendingIntent зарегистрирован, система помнит о нём и доставит его, даже если процесс приложения убит.
- Выстреливает она в Intent, а не в код. Сам
onReceiveдолжен быть коротким — длинную работу передавайте в WorkManager или JobScheduler, как и в обычном ресивере. - Повторная установка с тем же PendingIntent заменяет старую тревогу. Это часто используют для «сброса» таймера, но про это же забывают при отмене.
Суть в одной фразе
AlarmManager — это «будильник системы»: регистрируете PendingIntent с временем срабатывания, а доставку гарантирует ОС, а не ваше приложение.
Если задача нужна только пока приложение живо — AlarmManager не нужен вообще: Handler.postDelayed() справится лучше и дешевле. Документация прямо рекомендует его для тайминга внутри приложения.
Типы и точность тревог
У AlarmManager есть четыре константы типа, и их часто путают. Разница — в «часах» и в том, будит ли тревога устройство:
ELAPSED_REALTIME— отсчёт от загрузки устройства, устройство не будит.ELAPSED_REALTIME_WAKEUP— то же, но будит устройство.RTC— время по wall-clock (UTC), устройство не будит.RTC_WAKEUP— wall-clock + будит устройство.
Elapsed-типы удобны для интервалов («через полчаса»), RTC — для привязки к календарю («в 14:00»). Первые не зависят от часового пояса и смены времени на устройстве — поэтому документация рекомендует по возможности именно их.
val alarmManager = context.getSystemService(AlarmManager::class.java)
// Через полчаса от текущего момента, будит устройство
alarmManager.set(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 30 * 60 * 1000L,
pendingIntent
)Запоминается просто: ELAPSED_REALTIME — «время с момента загрузки», RTC — «реальное время». Суффикс _WAKEUP означает, что тревога разбудит устройство и не пропустит срок. Разбудить устройство без необходимости — плохая практика, система за это «не любит» приложение.
Второе деление — точность. Большинство приложений должно использовать неточные тревоги: система объединяет их от разных приложений и доставляет пакетами, экономя батарею.
set()— неточная, но «не раньше указанного времени». На Android 12+ система доставит её в течение часа от заданного момента (если не действуют Doze или режим экономии).setWindow()— неточная с окном доставки. Если приложение таргетит Android 12+, минимальное окно — 10 минут: значения меньше 600 000 мс просто обрезаются.setInexactRepeating()— повторяющаяся неточная тревога. Важный факт: с Android 4.4 (API 19) все повторяющиеся тревоги неточные. Интервалы берутся из констант (INTERVAL_FIFTEEN_MINUTES,INTERVAL_DAYи т.д.), свой интервал задать нельзя.
Точные тревоги — setExact(), setExactAndAllowWhileIdle() и setAlarmClock() — срабатывают в почти заданный момент. setAlarmClock() — вершина: систему она не интересует, будильник покажет пользователь, и система выйдет из энергосбережения ради доставки.
час
На Android 12+ неточная тревога через set() доставляется в пределах часа от заданного времени. «Почти точное» время гарантируют только точные тревоги.
Правильный ответ на собеседовании: «Точные тревоги нужны только там, где пользователь ждёт событие в конкретный момент: будильник, напоминание. Фоновая синхронизация — это неточная тревога или WorkManager». WorkManager с его периодическими задачами и окном выполнения закрывает 90% кейсов, для которых раньше брали AlarmManager.
Android 12+: разрешение на точные тревоги
Самый свежий подводный камень. Если приложение таргетит Android 12 (API 31) и выше и вызывает точную тревогу без разрешения — будет SecurityException.
Разрешение называется SCHEDULE_EXACT_ALARM — «Alarms & reminders» в системных настройках:
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />Нюансы, которые превращают простой вопрос в «душный»:
SCHEDULE_EXACT_ALARMвыдаёт пользователь, и его можно отозвать в любой момент. Приложениям, которые ставят точные тревоги, нужно проверять доступ перед каждой установкой — для этого естьAlarmManager.canScheduleExactAlarms().- Свежим установкам приложений, таргетящих Android 13+, разрешение не выдаётся автоматически.
- На Android 13+ есть альтернатива —
USE_EXACT_ALARM: она выдаётся автоматически и её нельзя отозвать, но сфера применения жёстко ограничена (по сути только будильники и таймеры) и она подпадает под политику Google Play. - Отправлять пользователя в настройки можно через Intent с
ACTION_REQUEST_SCHEDULE_EXACT_ALARM.
private fun scheduleExact(context: Context, triggerAtMillis: Long, pendingIntent: PendingIntent) {
val alarmManager = context.getSystemService(AlarmManager::class.java)
if (alarmManager.canScheduleExactAlarms()) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerAtMillis,
pendingIntent
)
} else {
// Показать экран с объяснением и ACTION_REQUEST_SCHEDULE_EXACT_ALARM
}
}Классический промах кандидата — ответить «просто вызвать setExact()». На Android 12+ это исключение, если нет разрешения. И вторая часть: если пользователь отозвал разрешение, все будущие точные тревоги отменяются — приложение должно обрабатывать этот сценарий.
Doze, перезагрузка и отмена
Тревога — хрупкая вещь, и кандидаты чаще всего спотыкаются на двух сценариях: Doze и перезагрузка.
Doze (Android 6+, API 23) — режим энергосбережения, в который устройство уходит, когда оно неподвижно и не используется. В Doze обычные тревоги не срабатывают: система откладывает их до выхода устройства из режима. Работать в Doze могут setExactAndAllowWhileIdle() и setAlarmClock(), а ещё — WorkManager, умеющий дожидаться выхода из режима и выполнять работу.
Перезагрузка. По умолчанию все тревоги стираются при выключении устройства. Если приложению нужно пережить перезагрузку — надо пересоздать тревоги после ACTION_BOOT_COMPLETED:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:name=".SampleBootReceiver"
android:enabled="false"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>Обратите внимание на android:enabled="false" — это рекомендованный паттерн: ресивер неактивен, пока приложение не включит его через setComponentEnabledSetting() в момент установки тревоги. Так система не будит приложение ради BOOT_COMPLETED, если тревог нет. И ещё один факт: ACTION_BOOT_COMPLETED доходит только если пользователь хотя бы раз запускал приложение.
Отмена тревоги — ещё одно место, где ловят на деталях. Отменять нужно через тот же PendingIntent, что и ставили. Чтобы получить ссылку на существующую тревогу, а не создать новую, используется FLAG_NO_CREATE:
val pi = PendingIntent.getBroadcast(
context,
REQUEST_CODE,
intent,
PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
)
if (pi != null) {
alarmManager.cancel(pi)
}- AlarmManager работает вне приложения: PendingIntent доставит система, даже если приложение убито.
- Четыре типа:
ELAPSED_REALTIME[_WAKEUP]иRTC[_WAKEUP];_WAKEUPбудит устройство. - Повторяющиеся тревоги неточные с Android 4.4; для большинства задач —
setInexactRepeating()или WorkManager. - Точные тревоги (
setExact,setExactAndAllowWhileIdle,setAlarmClock) на Android 12+ требуютSCHEDULE_EXACT_ALARM— иначеSecurityException. - В Doze обычные тревоги откладываются; после перезагрузки тревоги стираются — нужен
BOOT_COMPLETEDресивер.
Ошибки кандидатов
Ошибка №1: «AlarmManager — это таймер». Нет: таймер внутри приложения — Handler.postDelayed или корутины. AlarmManager решает задачу «сработать, даже если приложение закрыто и устройство спит».
Ошибка №2: «Точные тревоги — стандарт». Наоборот: точные тревоги — исключение для пользовательских сценариев вроде будильника, и на Android 12+ они ещё и под разрешением. Кандидат, который предлагает setExact для синхронизации данных, показывает, что не читал доки.
И ещё пара промахов, которые я слышу на собеседованиях:
- Забывать про Doze. Ответ «система доставит точно вовремя» верен только для
setExactAndAllowWhileIdle/setAlarmClock. Всё остальное система может отложить. - Ставить тревоги без учёта перезагрузки. «Будильник перестал срабатывать после перезагрузки» — классический баг, который на собеседовании превращается в вопрос «как пережить reboot».
- Отменять тревогу новым PendingIntent.
cancel()с другим Intent просто ничего не сделает — тревога останется жить.
Итоги
AlarmManager — небольшой по объёму, но богатый на подводные камни компонент. На собеседовании он проверяет, насколько свежие у вас знания платформы.
- Суть: тревога — зарегистрированный PendingIntent, который система доставляет в заданное время вне жизни приложения.
- Точность: большинство задач — неточные тревоги или WorkManager; точные — только для пользовательских сценариев и с разрешением
SCHEDULE_EXACT_ALARMна Android 12+. - Жизнь тревоги: Doze откладывает, перезагрузка стирает — нужен
BOOT_COMPLETEDресивер и правильная отмена через тот же PendingIntent.
Главное, что нужно запомнить
Три факта закрывают тему: повторяющиеся тревоги неточные с Android 4.4; точные тревоги на Android 12+ требуют разрешения пользователя; в Doze и после перезагрузки тревоги не выживают без специальной обработки. Скажете это — вопрос про AlarmManager закрыт.
Если чувствуете, что «знаю по отдельности, но на собеседовании в связке теряюсь» — это нормальная точка роста. Напишите в Telegram, разберём вашу ситуацию и составим план подготовки.