Как сделать сервис в Android работающим в фоне независимо от Activity
Что проверяют
Знание актуальных ограничений на фоновую работу, а не умение отнаследоваться от Service. Интервьюер хочет услышать, что выбор между foreground service и WorkManager определяется характером задачи, и что наивный startService() из фона на современных API просто упадёт.
Как отвечать
Service живёт в процессе приложения, а не в Activity: он переживает поворот экрана и уничтожение экрана, но умирает вместе с процессом. Требование «независимо от Activity» почти всегда сводится к одному из двух сценариев:
- работа видима пользователю и идёт прямо сейчас (проигрывание, навигация, запись трека, выгрузка файла): foreground service с постоянным уведомлением;
- работа отложенная, должна пережить перезапуск процесса и перезагрузку (синхронизация, отправка событий): WorkManager, а не сервис вовсе.
Ограничения, которые надо назвать вслух:
- с Android 8 (API 26) фоновый сервис убивается вскоре после ухода приложения в фон, а
startService()из фона бросаетIllegalStateException. Запуск делается черезContextCompat.startForegroundService(), и внутриonStartCommandв течение примерно 5 секунд обязан быть вызванstartForeground(), иначе прилетитForegroundServiceDidNotStartInTimeException; - с Android 12 (API 31) стартовать foreground service из фона нельзя вовсе, кроме перечисленного списка исключений (обработка FCM высокого приоритета, точный будильник, действие пользователя из уведомления);
- с Android 14 (API 34) обязателен
android:foregroundServiceTypeи парное разрешение видаFOREGROUND_SERVICE_DATA_SYNC; IntentServiceиAsyncTaskобъявлены устаревшими, предлагать их не стоит.
Сервис по умолчанию исполняется в главном потоке, свой поток создаётся руками:
class SyncService : Service() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification())
scope.launch {
runCatching { doWork() }
stopSelf(startId)
}
return START_STICKY
}
override fun onDestroy() {
scope.cancel()
super.onDestroy()
}
}<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<service
android:name=".SyncService"
android:exported="false"
android:foregroundServiceType="dataSync" />Код возврата из onStartCommand задаёт поведение после убийства процесса: START_STICKY перезапустит сервис с пустым интентом, START_REDELIVER_INTENT повторит последний интент, START_NOT_STICKY не будет перезапускать. Bound service под задачу не подходит: он умирает при отвязке последнего клиента.
Отдельно упомянуть Doze, ограничения по батарее и агрессивные киллеры у части вендоров: критичная работа должна быть идемпотентной и продублирована через WorkManager.
Где спотыкаются
Пересказывают схему из старой документации (IntentService плюс startService плюс AsyncTask), которая на API 26 и выше не запустится, и не могут объяснить разницу между отложенной задачей и работой, видимой пользователю.