Введение

**«Поставьте точный будильник на Android 12+» — и кандидат, написавший setExact без разрешения, получает SecurityException. Вопрос про AlarmManager проверяет не знание API, а то, следили ли вы за изменениями платформы за последние годы.

AlarmManager — компонент, который позволяет выполнять операции по расписанию вне жизненного цикла приложения: даже если приложение не запущено и экран выключен, система доставит ваш Intent в нужный момент. По сути это «будильник» для вашего кода.

Вопросы про него не так часты, как про Activity или Service, но коварны: кандидат помнит set() из старого туториала и не знает ничего про Doze и разрешения. Разберём тему целиком: как устроена тревога, какие бывают типы, что изменилось в Android 12+ и как тревоги переживают перезагрузку.

Как устроена тревога

Механика AlarmManager проста: вы регистрируете PendingIntent, который система «выстрелит» в заданное время. Обычно это broadcast-ресивер, который получает событие и запускает работу — например, ставит задачу в WorkManager.

AlarmScheduler.kt
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»). Первые не зависят от часового пояса и смены времени на устройстве — поэтому документация рекомендует по возможности именно их.

AlarmScheduler.kt
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» в системных настройках:

AndroidManifest.xml
<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.
ExactAlarmScheduler.kt
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:

AndroidManifest.xml
<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:

AlarmScheduler.kt
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, разберём вашу ситуацию и составим план подготовки.