Ошибка startForeground после обновления до Android 8.1: приложение крашится с ForegroundServiceDidNotStartInTimeException
Причина ошибки и правильное решение
Проблема заключается в том, что Android 8.1 (API 26+) ввел строгий тайм-аут для foreground-сервисов. После вызова Context.startForegroundService() система ожидает, что сервис вызовет startForeground() в течение 5 секунд. Если этого не происходит — приложение аварийно завершается с ForegroundServiceDidNotStartInTimeException. Ваш код может не успевать создать канал уведомлений или сам Notification вовремя.
Вот исправленная версия сервиса с правильным порядком действий и обработкой канала:
class MyForegroundService : Service() {
override fun onCreate() {
super.onCreate()
createNotificationChannel() // Создаем канал при создании сервиса
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 1. Сначала создаем уведомление
val notification = createNotification()
// 2. НЕМЕДЛЕННО вызываем startForeground
startForeground(1, notification)
// 3. Только после этого запускаем фоновую задачу
startBackgroundWork()
return START_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"my_channel",
"Мой канал",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Канал для фонового сервиса"
}
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
}
private fun createNotification(): Notification {
return NotificationCompat.Builder(this, "my_channel")
.setContentTitle("Сервис работает")
.setContentText("Выполняется загрузка...")
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build()
}
private fun startBackgroundWork() {
Thread {
// Ваша долгая фоновая задача
Thread.sleep(5000) // Пример
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
}.start()
}
override fun onBind(intent: Intent?): IBinder? = null
}
Как правильно запускать сервис
В Activity или Fragment используйте следующий код для запуска:
val intent = Intent(this, MyForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
Важные моменты
- Канал уведомлений должен быть создан ДО вызова
startForeground(). Лучше всего создавать его вonCreate()сервиса или вApplication.onCreate(). - Тайм-аут 5 секунд — система дает максимум 5 секунд на вызов
startForeground()послеstartForegroundService(). Не делайте долгих операций до этого вызова. - Используйте
NotificationCompatиз библиотеки AndroidX для обратной совместимости. - Если сервис запускается из BroadcastReceiver или JobScheduler — убедитесь, что контекст не устарел.
Дополнительная проверка
Убедитесь, что в манифесте у сервиса нет атрибута android:foregroundServiceType, если вы не используете соответствующие разрешения (например, для камеры или микрофона). Для простых сервисов этот атрибут не нужен.
Альтернативный подход: использование WorkManager для фоновых задач
Если ваша цель — выполнить фоновую задачу, а не держать сервис постоянно работающим, рассмотрите использование WorkManager — это рекомендуемый Google способ для фоновой работы, начиная с Android 8.1.
Вот пример реализации:
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// Ваша фоновая задача
Thread.sleep(5000)
return Result.success()
}
}
// Запуск в Activity
val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(this).enqueue(workRequest)
WorkManager автоматически обрабатывает жизненный цикл, тайм-ауты и совместимость с разными версиями Android. Он не требует создания foreground-сервиса для обычных фоновых задач. Если вам всё же нужно уведомление о прогрессе — используйте ForegroundInfo внутри Worker.
Этот подход решает проблему с ForegroundServiceDidNotStartInTimeException полностью, так как WorkManager сам управляет вызовом startForeground() в нужный момент.
Ответов пока нет.
Войдите чтобы оставить ответ