Надёжные напоминания на Android в 2026 году: WorkManager, точные будильники и новые правила батареи
Как в 2026 году выпускать напоминания на Android, которые действительно срабатывают — WorkManager против AlarmManager, SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS и причуды производителей, которые всё ещё кусаются.
Приложение-напоминалка на Android работает, только если напоминание появляется. Звучит очевидно — пока вы не выпустите такое приложение. Тогда вы обнаруживаете режим doze, корзины ожидания приложений (app standby buckets), оптимизаторы батареи от производителей, разрешение SCHEDULE_EXACT_ALARM, разрешение времени выполнения POST_NOTIFICATIONS, ограничения на службы переднего плана и тот прелестный факт, что Samsung и Xiaomi молча убьют вашу запланированную работу ради «экономии батареи», как бы правильно вы её ни написали.
Я сделал Hydrame — приложение для отслеживания воды, всё ценностное предложение которого сводится к «напомни мне в нужный момент, мягко». Я потратил больше времени, чем хотел бы признать, заставляя стек напоминаний Android быть надёжным. Этот пост — версия 2026 года того сценария, который наконец работает.
Проблема напоминаний в одном абзаце
Android агрессивен в уничтожении фоновой работы, и так и должно быть — если этого не контролировать, каждое приложение будило бы радиомодуль каждые несколько минут, и ваша батарея умирала бы к обеду. Для честных приложений-напоминалок результат — это нагромождение перекрывающихся API, каждый из которых ведёт себя немного по-разному в разных версиях и оболочках производителей. Задача в том, чтобы выбрать правильный примитив под ваш уровень «должно быть точно» и смириться с тем, что остальное — это тестирование на реальных устройствах.
Два примитива, которые имеют значение в 2026 году:
WorkManager— для неточной, откладываемой работы. Периодические проверки, сценарии вроде «напомни мне когда-нибудь в течение следующего часа». Учитывает батарею, объединяется, работает даже после перезагрузок.AlarmManager.setExactAndAllowWhileIdle— для точных, видимых пользователю уведомлений в конкретный момент. Дорогой. Используйте экономно и только тогда, когда «промах на 15 минут» сделал бы функцию сломанной.
Если ваше напоминание должно сработать ровно в 19:30, вам нужен точный будильник. Если «где-то около 19:30» подходит, используйте WorkManager. Большинство приложений по умолчанию выбирают неправильный.
Три разрешения, которые нужно настроить правильно
В 2026 году три разрешения времени выполнения/установки решают, увидит ли пользователь напоминание вообще:
POST_NOTIFICATIONS(время выполнения, Android 13+). Без него ваше уведомление буквально не появляется. Запрашивайте в правильный момент, а не при первом запуске.SCHEDULE_EXACT_ALARM(Android 12+, ограничено в 13+). Требуется дляsetExactAndAllowWhileIdle. На Android 14+ оно предоставляется только после того, как пользователь явно включит его в системных настройках; приложение обязано отправить пользователя туда.USE_EXACT_ALARM(Android 13+, специальное использование). Для приложений, где точные будильники — это ядро (будильники, напоминания календаря, гидратация). Play требует обоснования.
Манифест Hydrame выглядит так:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
RECEIVE_BOOT_COMPLETED — то, что большинство забывает: без него ваши запланированные будильники исчезают при следующей перезагрузке. Перепланируйте из BroadcastReceiver по ACTION_BOOT_COMPLETED.
Запрос POST_NOTIFICATIONS в правильный момент
Не запрашивайте при первом запуске. Процент согласия резко выше, если вы спрашиваете в момент, соответствующий ценности: ровно тогда, когда пользователь включает своё первое напоминание. Реальный поток:
@Composable
fun EnableReminderButton(onEnabled: () -> Unit) {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
) { granted ->
if (granted) onEnabled()
}
FilledButton(onClick = {
if (Build.VERSION.SDK_INT >= 33) {
val state = ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS,
)
if (state == PackageManager.PERMISSION_GRANTED) onEnabled()
else launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
} else {
onEnabled()
}
}) { Text("Enable reminders") }
}
Ещё две вещи, которые я делаю всегда:
- Обоснование второго шанса. Если пользователь отказал, не спрашивайте снова сразу. В следующий раз, когда он попытается включить напоминание, покажите небольшое встроенное сообщение с кнопкой, которая открывает экран настроек приложения, чтобы он мог переключить его сам.
- Определяйте состояние «отказано с «больше не спрашивать»», запоминая последний отказ в DataStore —
shouldShowRequestPermissionRationaleсам по себе для этого ненадёжен.
Неточно: периодическая работа WorkManager
Для напоминания, где «когда-нибудь утром» приемлемо, WorkManager — скучный правильный ответ:
class DailyHydrationCheckWorker(
appContext: Context,
params: WorkerParameters,
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
scheduleTodayReminders()
return Result.success()
}
}
fun scheduleDailyHydrationCheck(context: Context) {
val request = PeriodicWorkRequestBuilder<DailyHydrationCheckWorker>(
repeatInterval = 1, repeatIntervalTimeUnit = TimeUnit.DAYS,
)
.setConstraints(
Constraints.Builder()
.setRequiresBatteryNotLow(true)
.build(),
)
.setInitialDelay(secondsUntil(6, 0).toLong(), TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"daily_hydration_check",
ExistingPeriodicWorkPolicy.UPDATE,
request,
)
}
Несколько моментов, на которых люди спотыкаются:
- Минимальный период 15 минут.
PeriodicWorkRequestне будет запускаться чаще, чем раз в 15 минут, что бы вы ни просили. Для планирования короче 15 минут используйте одноразовую работу, которая связывает сама себя в цепочку. - Нет точного тайминга. WorkManager планирует внутри окна. Если вам нужно «ровно в 8:00:00», это неправильный примитив.
- Battery-not-low дружелюбно к вашим пользователям и к системе; включайте, если нет причины не делать этого.
Точно: AlarmManager.setExactAndAllowWhileIdle
Когда напоминание должно сработать в конкретный момент — например, пользователь сказал «напомни мне в 19:30» и имеет это в виду всерьёз — вам нужен точный будильник. Шаблон:
fun scheduleExactReminder(
context: Context,
triggerAtMillis: Long,
requestCode: Int,
) {
val am = context.getSystemService(AlarmManager::class.java)
if (Build.VERSION.SDK_INT >= 31 && !am.canScheduleExactAlarms()) {
// Send the user to the system page to grant the permission.
context.startActivity(
Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
.setData(Uri.parse("package:${context.packageName}"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
return
}
val intent = Intent(context, ReminderReceiver::class.java)
.putExtra("requestCode", requestCode)
val pending = PendingIntent.getBroadcast(
context, requestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pending)
}
setExactAndAllowWhileIdle — та, что уважает doze. Используйте setAlarmClock вместо неё только если ваше приложение действительно будильник — оно показывает чип следующего будильника в системном интерфейсе и получает наивысший приоритет.
Приёмник + служба переднего плана для тяжёлого момента
Когда будильник срабатывает, у вас есть несколько секунд бодрствования, чтобы отправить уведомление. Для простого напоминания «динь!» этого достаточно:
class ReminderReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Post the notification synchronously.
val nm = context.getSystemService(NotificationManager::class.java)
nm.notify(
intent.getIntExtra("requestCode", 0),
buildHydrationNotification(context),
)
// Re-schedule the next one.
scheduleNext(context)
}
}
Если вам нужно выполнить настоящую работу по будильнику (синхронизация, загрузка), немедленно повышайте до службы переднего плана. Не пытайтесь хитрить — система отзовёт вас через мгновение, если вы застрянете в приёмнике.
Перезагрузки и смена часового пояса
Два события молча ломают наивную реализацию:
- Перезагрузка. Будильники стираются. Слушайте
ACTION_BOOT_COMPLETEDи перепланируйте из вашего сохранённого состояния. - Смена часового пояса. Напоминание на 7 утра не должно срабатывать в 7 по местному времени до переезда пользователя и в 7 по времени Стамбула после. Слушайте
ACTION_TIMEZONE_CHANGEDи пересчитывайте.
Один приёмник обрабатывает оба:
<receiver android:name=".BootAndTimezoneReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.TIMEZONE_CHANGED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter>
</receiver>
MY_PACKAGE_REPLACED — то, что срабатывает при обновлениях приложения: ваш код свежий, но ваши будильники исчезли. Перепланируйте и там.
Оптимизаторы производителей — тихие убийцы
В 2026 году худший из оставшихся нарушителей — всё ещё список Samsung «Спящие приложения», а сразу за ним автозапуск и энергосбережение Xiaomi. Две практические защиты:
- Диагностический баннер внутри приложения. Если уведомления не срабатывали в течение 24 часов, а у пользователя включены напоминания, покажите небольшой баннер с диплинком «Исправить это» на нужную страницу настроек производителя. Stack Overflow и проект Don’t Kill My App поддерживают эти диплинки в актуальном состоянии; кэшируйте их локально.
- Не запрашивайте белый список батареи с рвением. Это пугает пользователей, и Play смотрит на это косо. Приберегите это для пользователя, который уже видел пропущенное напоминание.
Схема, которая в итоге сработала
После трёх итераций над Hydrame архитектура, которая наконец показалась надёжной:
- Одна периодическая задача
WorkManagerзапускается каждую ночь. Она читает расписание пользователя и вставляет точные будильники на следующие 24 часа. - Каждый точный будильник отправляет уведомление через
BroadcastReceiver. Приёмник глуп — никаких обращений к БД, никакой сети. - Приёмники перезагрузки + смены часового пояса + обновления приложения вручную запускают ту же ночную задачу, чтобы воссоздать расписание.
- Метрика пульса (метка времени последнего успешного уведомления, хранимая в DataStore) питает баннер внутри приложения «ваши напоминания могут быть убиты вашим телефоном».
Это больше проводки, чем я написал бы, если бы доверился своему первому порыву («просто используй WorkManager»). Но каждый кусок существует потому, что без него что-то молча ломалось.
Тестирование на реальных телефонах, снова
Повторю ту же строчку из каждого поста об Android, который пишу: надёжные напоминания нельзя протестировать только на эмуляторе. Возьмите дешёвый Samsung, дешёвый Xiaomi и Pixel. Поставьте напоминание через 30 минут. Заблокируйте телефон и оставьте его на столе. Вернитесь и посмотрите, что сработало, а что нет. Этот единственный эксперимент стоит больше, чем любой пост в блоге.
Включая этот. Идите и протестируйте.
// По теме
Ещё из журнала
Правильные действия в уведомлениях Android: отметить приём дозы без открытия приложения
Практическое руководство по кнопкам действий в уведомлениях Android — PendingIntent, ограничение activity-трамплина, goAsync() и безопасное обновление состояния Room.
Как выстроить режим приёма лекарств, которого вы действительно будете придерживаться
Практическое руководство по соблюдению режима приёма лекарств — почему люди пропускают дозы, как на самом деле работает время напоминаний и как выстроить режим, который переживёт плохую неделю.
Привычка пить воду, которая не держится на силе воли
Большинство советов про питьевой режим сводятся к «пей больше воды» в надежде, что дальше сработает сила воли. Вот привычка, построенная на подсказках и мягких напоминаниях — вместо этого.