Работа с фоновыми задачами в Android 12: переезжаем с foreground service на expedited jobs
С релизом 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.
Сейчас setForegroundAsync() объявлен устаревшим, а при попытке запустить сервис из бэкграунда на выходе будет ForegroundServiceStartNotAllowedException.
Expedited jobs
Этот тип джобов позволяет приложениям выполнять короткие и важные задачи, давая системе больше контроля над доступом к ресурсам. Он находится где-то между foreground-сервисами и привычными джобами WorkManager. От последних их отличает:
минимально отложенное время запуска;
обход ограничений Doze-mode на использование сети;
меньшая вероятность быть «убитыми» системой.
А ещё в них не поддерживаются ограничения по заряду батареи и режиму работы девайса:
У expedited job больший приоритет на ускоренный запуск, поэтому операционная система строже регулирует их количество. Например, если попытаться запланировать джобу при исчерпаном лимите, то сразу вернётся JobScheduler#RESULT_FAILURE.
Если же ограничения по квоте, сети и памяти устройства выполнены, то у джобы будет около минуты на выполнение своих функций. Иногда больше, но это сильно зависит от лимита и общей загруженности системы.
Миграция foreground service на expedited job
Стандартный сервис для выполнения фоновых задач обычно выглядит примерно так:
А запускается так:
Поговорим о том, как перевести этот сервис на expedited job. Происходит это буквально в три простых шага.
1. Подключаем WorkManager к проекту:
2. Создаём класс, наследующийся от Worker (он будет выполнять задачу, которую раньше делал сервис):
3. Создаём WorkRequest и передаём его для запуска в WorkManager:
Здесь есть важный параметр OutOfQuotaPolicy, который отвечает за поведение при невозможности запустить джобу немедленно. Он существует в двух вариантах:
RUN_AS_NON_EXPEDITED_WORK_REQUEST — при заполненной квоте запустится обычная джоба, не expedited.
DROP_WORK_REQUEST — при заполненной квоте запрос на выполнение сразу зафейлится.
На этом, собственно, базовая миграция заканчивается.
Вместо заключения
Переехать на expedited job довольно легко, особенно, если в проекте уже подключен WorkManager.
Сейчас пропала необходимость держать нотификацию в статус-баре, а в условиях выполнения задачи появилась дополнительная гибкость благодаря возможностям WorkManager. Например, теперь можно пережить «смерть» процесса, тонко настраивать ретраи, периодичность выполнения задач и многое другое.
Источник
Полный список
— изучаем IntentService
— включаем режим Foreground для сервиса
— помещаем сервис в автозагрузку
Строили мы, строили, и, наконец, построили. Урок номер 100, с чем всех нас и поздравляю )
В этом уроке рассмотрим еще несколько полезных вещей про сервисы. Выносить каждую из них в отдельный урок я не стал, вполне можно в одном все рассмотреть. Проекты здесь тоже создавать не будем, чтобы урок не получился слишком громоздким. Я просто приведу некоторые куски кода и скрины для наглядности своих рассуждений. А если у вас будет желание, вы по этим наработкам сами можете создать проекты-примеры.
IntentService
Это подкласс обычного Service. Он используется, если вам в сервисе надо выполнять какие-то тяжелые задачи, и вы не хотите сами возиться с асинхронностью. Принцип работы этого вида сервиса прост. Он создает новый поток для своей работы. Затем берет все Intent пришедшие ему в onStartCommand и отправляет их на обработку в этот поток. Как именно обрабатываются Intent – зависит от нас, т.к. мы сами кодим это в методе onHandleIntent.
Т.е. приложение сыпет в сервис вызовами startService, в которых передает Intent-ы. IntentService принимает эти вызовы в onStartCommand, берет Intent-ы и отправляет их в очередь на обработку. И далее они поочередно обрабатываются в отдельном потоке методом onHandleIntent. Когда последний Intent из очереди обработан, сервис сам завершает свою работу.
В приложении делаем три вызова:
Где time – это время паузы, которую будем делать в сервисе, а label – просто метка, чтобы отличать вызовы.
Здесь необходим конструктор, в котором вызываем конструктор супер-класса и указываем какое-нить имя. Оно будет использовано для наименования потока.
В методе onHandleIntent кодим обработку Intent-ов. Достаем из них time и label, запускаем паузу на time секунд и выводим в лог label в начале и в конце.
В итоге, при запуске в логах видим:
11:07:37.880: D/myLogs(4137): onCreate
11:07:37.880: D/myLogs(4137): onHandleIntent start Call 1
11:07:40.880: D/myLogs(4137): onHandleIntent end Call 1
11:07:40.880: D/myLogs(4137): onHandleIntent start Call 2
11:07:41.880: D/myLogs(4137): onHandleIntent end Call 2
11:07:41.880: D/myLogs(4137): onHandleIntent start Call 3
11:07:45.890: D/myLogs(4137): onHandleIntent end Call 3
11:07:45.890: D/myLogs(4137): onDestroy
Сервис создался, вызовы выполнились по очереди и сервис завершил работу. От нас понадобилось только накодить обработку.
Foreground
Вы можете сказать системе, что ваш сервис очень важен для пользователя и его нельзя грохать при нехватке памяти. Это актуально, например, для музыкального плеера. В статус-бар при этом будет помещено уведомление.
На вход он принимает те же параметры, что и NotificationManager.notify – ID и Notification.
Т.е. вы создаете уведомление, назначаете ему ID и передаете это в startForeground. Сервис переходит в режим IDDQD :), а в статус-баре появилось уведомление.
Оно появилось в разделе для постоянных уведомлений (Ongoing).
Метод stopForeground (boolean removeNotification) — возвращает сервису способность быть убитым системой в случае острой нехватки памяти. А на вход он принимает boolean-значение – удалять уведомление из статус-бара или нет.
Уведомление также пропадет, когда сервис будет остановлен.
Эти методы работают, начиная с Android 2.0. Пример реализации для более ранних версий есть в хелпе.
Напомню, что уведомления мы научились создавать на прошлом уроке.
Автозагрузка
Сервисы для получения погоды или почты имеет смысл помещать в автозагрузку. Для этого нам надо создать BroadcastReceiver, настроить его IntentFilter на Action = android.intent.action.BOOT_COMPLETED, и добавить права android.permission.RECEIVE_BOOT_COMPLETED. Этот BroadcastReceiver будет вызван системой при старте системы и в нем мы кодим запуск сервиса.
Допустим, есть проект с сервисом MyService.
Создаем в проекте класс MyBroadReceiv
В манифесте добавляем его как Receiver и настраиваем фильтр
Добавляем права на получение сообщения о загрузке
Инсталлим проект на AVD. Закрываем AVD. Запускаем через меню в Eclipse: Window > AVD Manager. Находим там наш эмулятор и запускаем вручную.
Когда он запустился, смотрим логи
onReceive android.intent.action.BOOT_COMPLETED
MyService onCreate
MyService onStartCommand
Сработал BroadcastReceiver и запустил сервис.
Если после запуска AVD логи не отображаются, то откройте DDMS и во вкладке Devices явно выберите ваш AVD.
P.S. Я уже писал об этом, но напишу еще раз. Последующие уроки будут выходить по более свободному графику. Следите за обновлениями.
На следующем уроке:
— создаем свой ContentProvider
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Foreground Services in Android
Jun 10, 2020 · 4 min read
In this tutorial, I’ll talk about the foreground service on Android. We will go through it’s implementation and some pointers I’d like to talk about. So let’s get started.
So what is a foreground service?
Official Android documentation defines a foreground service as
A foreground service performs some operation that is noticeable to the user. For example, an audio app would use a foreground service to play an audio track. Foreground services must display a Notification. Foreground services continue running even when the user isn’t interacting with the app.
A foreground service is a service that the user is active l y aware of and isn’t a candidate for the system to kill when low on memory. A foreground service must provide a notification for the status bar, which is placed under the Ongoing heading. This means that the notification cannot be dismissed unless the service is either stopped or removed from the foreground.
The real-world use cases of foreground services can be a Google Map app that narrates directions, or a music app that constantly streams music, even if the app is in the background.
Creating a Foreground Service takes the following steps.
- Start a Service, a Sticky Service that sticks to the Application.
- Display a notification to let Android know about the foreground service
- Once your notification is displayed, implement the logic for the Foreground Service. This is where you stream the music or map directions
- Update the notification respectively. Note: Until the Foreground service is completely killed, notification has to be displayed
- Once the work is done, kill your Service.
We start by creating our service class that extends the Service class
Override the onStartCommand to start a notification. If you miss this step, Android will kill the service after around a 1 minute of execution time.
Explanation
Jumping straight to the explanation of what we have done here. There are 2 important things to know about the Foreground Service.
- As mentioned before, the foreground services in Android cannot be started without passing a Notification to it.
- Service Lifecycle decides if a Service should be running or not. When it comes to overriding the onStartCommand() method, we are required to provide return modes which are:
- START_STICKY: If this service’s process is killed while it is started (after returning from onStartCommand(Intent, int, int) ), then leave it in the started state but don’t retain this delivered intent. This mode makes sense for things that will be explicitly started and stopped to run for arbitrary period of time, such as a service performing background music playback.
- START_NOT_STICKY: If this service’s process is killed while it is started (after returning from onStartCommand(Intent, int, int) ), and there are no new start intents to deliver to it, then take the service out of the started state and don’t recreate until a future explicit call to Context#startService . This mode makes sense for things that want to do some work as a result of being started but can be stopped when under memory pressure and will explicitly start themselves again later to do more work. An example of such a service would be one that polls for data from a server: it could schedule an alarm to poll every N minutes by having the alarm start its service.
- START_REDELIVER_INTENT: if this service’s process is killed while it is started (after returning from onStartCommand(Intent, int, int) ), then it will be scheduled for a restart and the last delivered Intent re-delivered to it again via onStartCommand(Intent, int, int) . This Intent will remain scheduled for redelivery until the service calls stopSelf(int) with the start ID provided to onStartCommand(Intent, int, int) . The service will not receive a onStartCommand(android.content.Intent, int, int) call with a null Intent because it will only be restarted if it is not finished processing all Intents sent to it (and any such pending events will be delivered at the point of restart).
And lastly, for apps that are targeting Android API level P and beyond, put this in your AndroidManifest.xml
And also, register your service in the same file
Once you have your service class setup, all you need to do is call the startService method from your Activity/Fragment and viola! A notification will popup. In order to stop the service, call the stopService function.
- If you call startService from the background, it will throw an IllegalStateException and crash
- You can use the startForegroundService (or ContextCompat.startForegroundService) to let the system know about the nature of your service. If you do this, you need to call the startForeground method from your service class within 5 seconds
Check a complete sample out on my Github Repository, I will post more articles containing background work, work scheduler, etc.
Источник