Как стать автором
Обновить
212.9
Рейтинг
FUNCORP
Разработка развлекательных сервисов

Работа с фоновыми задачами в Android 12: переезжаем с foreground service на expedited jobs

Блог компании FUNCORP Разработка мобильных приложений *Разработка под Android *Kotlin *

С релизом Android 12 приложения, где новая версия операционки будет указана в targetSdkVersion, получат запрет на запуск foreground-сервисов в бэкграунде. В качестве альтернативы Google предлагает WorkManager, который с появлением expedited jobs станет предпочтительным вариантом для запуска высокоприоритетных фоновых задач. 

О нём и пойдёт речь в статье — под катом обсудим новые возможности инструмента, подключим его к приложению и реализуем миграцию с foreground-сервиса.

WorkManager и foreground service 

Для справки: 

  • Foreground service — это какой-либо сервис, о котором знает пользователь через нотификацию в статус-баре. Например, воспроизведение музыки или работа GPS в картах.

  • WorkManager — это API для планирования задач, которые будут выполняться, даже если выйти из приложения или перезагрузить устройство.

WorkManager уже давно является приоритетным способом выполнения длительных фоновых задач. К таким относятся синхронизация данных с бэкендом, отправка аналитики, периодическая проверка свободного места в системе с помощью PeriodicWork и так далее.

Но в WorkManager присутствовал и недостаток — не было никаких гарантий, что джоба начнётся незамедлительно после создания. В версии 2.3.0 разработчики добавили для воркеров метод setForegroundAsync(), который, по сути, превращал фоновую задачу в foreground-сервис и позволял немедленно приступить к её выполнению.

Такой подход ничем особо не отличался от разработки foreground-сервиса вручную, когда необходимо создавать объекты Notification и NotificationChannel при таргете выше, чем на Android Nougat.

private fun createInfo(): ForegroundInfo {
   return ForegroundInfo(getNotificationId(), createNotification())
}

Сейчас setForegroundAsync() объявлен устаревшим, а при попытке запустить сервис из бэкграунда на выходе будет ForegroundServiceStartNotAllowedException

И тут на сцену выходят expedited jobs.

Expedited jobs

Этот тип джобов позволяет приложениям выполнять короткие и важные задачи, давая системе больше контроля над доступом к ресурсам. Он находится где-то между foreground-сервисами и привычными джобами WorkManager. От последних их отличает: 

  • минимально отложенное время запуска;

  • обход ограничений Doze-mode на использование сети; 

  • меньшая вероятность быть «убитыми» системой.

А ещё в них не поддерживаются ограничения по заряду батареи и режиму работы девайса:

 val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.NOT_ROAMING)
   .setRequiresStorageNotLow(true)
   /*
    Неподдерживаемые ограничения
   .setRequiresCharging(false)
   .setRequiresDeviceIdle(false)
   .setRequiresBatteryNotLow(false)
   */
   .build()

У expedited job больший приоритет на ускоренный запуск, поэтому операционная система строже регулирует их количество. Например, если попытаться запланировать джобу при исчерпаном лимите, то сразу вернётся JobScheduler#RESULT_FAILURE.

Если же ограничения по квоте, сети и памяти устройства выполнены, то у джобы будет около минуты на выполнение своих функций. Иногда больше, но это сильно зависит от лимита и общей загруженности системы. 

Миграция foreground service на expedited job

Стандартный сервис для выполнения фоновых задач обычно выглядит примерно так:

class ExampleService : Service() {
   override fun onBind(intent: Intent?): IBinder? {
       return null
   }

   override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
       val notification: Notification = createNotification()
       startForeground(UPLOAD_ID, notification)
       runHeavyJob()
       return START_NOT_STICKY
   }

   private fun createNotification(): Notification {
       val pendingIntent: PendingIntent =
           Intent(this, MainActivity::class.java).let { notificationIntent ->
               PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
           }

       val nb = NotificationCompat.Builder(this, createNotificationChannel())

       return buildNotification(nb)
   }

   private fun runHeavyJob() {
       //some great stuff
   }
}

А запускается так:

private fun startHeavyTask() {
   Intent(this, ExampleService::class.java).also { intent ->
       startService(intent)
   }
}

Поговорим о том, как перевести этот сервис на expedited job. Происходит это буквально в три простых шага.

1. Подключаем WorkManager к проекту:

implementation 'androidx.work:work-runtime:2.7.0-alpha04'

2. Создаём класс, наследующийся от Worker (он будет выполнять задачу, которую раньше делал сервис):

class ExampleWorker(appContext: Context, workerParams: WorkerParameters) :
   Worker(appContext, workerParams) {

   override fun doWork(): Result {
       runHeavyTask()
       return Result.success()
   }

   @SuppressLint("CheckResult")
   private fun runHeavyTask() {
       //some great stuff
   }
}

3. Создаём WorkRequest и передаём его для запуска в WorkManager:

fun runHeavyWork() {

   val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.CONNECTED)
       .setRequiresStorageNotLow(true)
       .build()

   val heavyWorkRequest: WorkRequest =
       OneTimeWorkRequest.Builder(ExampleWorker::class.java)
           .setConstraints(constraints)           .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
           .build()

   WorkManager
       .getInstance(context)
       .enqueue(heavyWorkRequest)
}

Здесь есть важный параметр OutOfQuotaPolicy, который отвечает за поведение при невозможности запустить джобу немедленно. Он существует в двух вариантах:

  • RUN_AS_NON_EXPEDITED_WORK_REQUEST — при заполненной квоте запустится обычная джоба, не expedited.

  • DROP_WORK_REQUEST — при заполненной квоте запрос на выполнение сразу зафейлится.

На этом, собственно, базовая миграция заканчивается.

Вместо заключения 

Переехать на expedited job довольно легко, особенно, если в проекте уже подключен WorkManager.

Сейчас пропала необходимость держать нотификацию в статус-баре, а в условиях выполнения задачи появилась дополнительная гибкость благодаря возможностям WorkManager. Например, теперь можно пережить «смерть» процесса, тонко настраивать ретраи, периодичность выполнения задач и многое другое.

Теги: androidforeground serviceandroid 12expedited jobsjobsworkmanagerkotlinфоновые задачиразработка мобильных приложениймобильные приложения
Хабы: Блог компании FUNCORP Разработка мобильных приложений Разработка под Android Kotlin
Всего голосов 60: ↑60 и ↓0 +60
Комментарии 19
Комментарии Комментарии 19

Похожие публикации

Лучшие публикации за сутки

Информация

Дата основания
Местоположение
Кипр
Сайт
fun.co
Численность
101–200 человек
Дата регистрации
Представитель
Account_is_busy

Блог на Хабре