Avatar ·

Ошибка startForeground после обновления до Android 8.1: приложение крашится с ForegroundServiceDidNotStartInTimeException

Всем привет! Я начинающий Android-разработчик на Kotlin. После обновления моего устройства на Android 8.1 (Oreo) мое приложение стало падать с ошибкой при запуске фонового сервиса. Раньше на Android 7.0 всё работало отлично. Вот мой код сервиса: ```kotlin class MyForegroundService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val notification = Notification.Builder(this, "my_channel") .setContentTitle("Сервис работает") .setContentText("Выполняется загрузка...") .setSmallIcon(R.drawable.ic_notification) .build() startForeground(1, notification) // ... долгая фоновая задача return START_STICKY } override fun onBind(intent: Intent?): IBinder? = null } ``` А вот как я запускаю сервис из Activity: ```kotlin startService(Intent(this, MyForegroundService::class.java)) ``` Ошибка в логах: `android.app.ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground()`. Я понимаю, что Android 8.1 требует использовать `startForegroundService()` вместо `startService()`, но даже после замены ошибка остаётся. Иногда приложение крашится не сразу, а через несколько секунд. Я создал канал уведомлений в Application.onCreate(), но это не помогло. Подскажите, как правильно исправить эту проблему? <<>>

Причина ошибки и правильное решение

Проблема заключается в том, что 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() в нужный момент.