Как запускать задачи по расписанию в Android, когда приложение выключено или выгружено из памяти
Что проверяют
Знаете ли вы, что процесс приложения может быть убит в любой момент, и какие механизмы системы это переживают. Главное, выбрать инструмент под требование: гарантированное выполнение когда-нибудь или точное время. Следом спрашивают про Doze и про то, что будет после перезагрузки.
Как отвечать
Основной инструмент это WorkManager. Задачи он хранит в своей базе, поэтому они переживают выгрузку процесса и перезагрузку, а на современных версиях Android работает поверх JobScheduler. Когда условия выполнены, система сама поднимет процесс и вызовет ваш Worker или CoroutineWorker:
- периодические задачи:
PeriodicWorkRequest, минимальный интервал 15 минут, точное время не гарантируется; - ограничения:
Constraintsна сеть, зарядку, уровень батареи; - уникальность:
enqueueUniquePeriodicWorkсExistingPeriodicWorkPolicy.KEEP, чтобы каждый запуск приложения не плодил дубли; - срочная короткая работа:
setExpedited().
Если нужно точное время (будильник, напоминание календаря), берут AlarmManager.setExactAndAllowWhileIdle() или setAlarmClock(). С Android 12 для точных будильников нужно разрешение SCHEDULE_EXACT_ALARM, а с Android 14 оно по умолчанию не выдаётся большинству новых установок, поэтому проверяйте canScheduleExactAlarms(). Будильники стираются при перезагрузке, их восстанавливают в ресивере BOOT_COMPLETED.
Долгую работу, которую видит пользователь (загрузка файла, трекинг тренировки), делают через foreground service с уведомлением и объявленным foregroundServiceType.
Где спотыкаются
Не знают про принудительную остановку. После «Остановить» в настройках система снимает все будильники и задачи приложения, и до ручного запуска ничего не сработает. Оболочки некоторых производителей ведут себя похоже даже при смахивании из недавних.
Ждут от WorkManager точности. Doze и App Standby Buckets сдвигают выполнение на часы, для задачи «ровно в 9:00» это неверный инструмент.
Копируют старые примеры с PendingIntent и флагом 0. При targetSdk 31 и выше без FLAG_IMMUTABLE или FLAG_MUTABLE приложение падает при создании интента.