- IntentService is deprecated #216
- Comments
- AimFirst commented Feb 5, 2020
- philips77 commented Feb 12, 2020
- AndresQuiVal commented May 27, 2020
- philips77 commented May 27, 2020
- Полный список
- IntentService
- Foreground
- Автозагрузка
- Планирование задач в Android с использованием JobScheduler и IntentService
- Суть вопроса и поднятых проблем
- Пример создания проекта с JobScheduler и IntentService
- Добавление IntentService для обработки задач в фоне
- Добавление BroadcastReceiver для инициализации задач
- Реализация наследника JobService для обработки событий от JobScheduler
- Создание новой задачи для JobScheduler
- Идентификатор задачи и ComponentName
- Инициализация параметров с помощью JobInfo.Builder
- Периодические задачи
- Критерий повтора выполнения задачи
- Отправка задачи на выполнение
- Проверка и отладка
- Выполнение задачи в отдельных потоках и оповещение сервиса о завершении
IntentService is deprecated #216
Comments
AimFirst commented Feb 5, 2020
DfuBaseService extends IntentService. However, IntentService is deprecated. There’s a few known issues with them not working reliably (for example on a galaxy J7). Is there any plan to update the base service to use something newer (JobIntentService perhaps)?
The text was updated successfully, but these errors were encountered:
philips77 commented Feb 12, 2020
Hello,
IntentService is not deprecated. The JobIntentService has slightly different purpose, it doesn’t have to be started immediately, and this is the behavior we want in DFU library. IntentService is just a service, that starts a thread to perform a task and stops itself when the task is complete.
There are of course some restrictions related to background execution limits on Android 8+, but that’s why the DfuServiceInitiator has setForeground(boolean) method, which defaults to true. This will show a notification to the user, but that is intended. The notification can be customized by extending updateForegroundNotification(NotificationCompat.Builder builder) method in the Dfu Service implementation.
Besides, what issues are you talking about?
AndresQuiVal commented May 27, 2020
Official Android documentation tells android IntentService is deprecated on API Level 26+, refer to:
https://developer.android.com/reference/android/app/IntentService
philips77 commented May 27, 2020
It’s deprecated in Android R, that is API 30. The lib starts the service in foreground to avoid background execution limits problems. Usually, when user clicks «update», they want the job done now, not in some future.
I’ll think about it. In mcumgr lib, for updates of devices running firmware based on nRF Connect SDK, we provide lower level implementation, where it is the user that needs to implement the service or a job.
Источник
Полный список
— изучаем 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 для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Планирование задач в Android с использованием JobScheduler и IntentService
Иногда при разработке под OS Android возникает необходимость выполнять ресурсоемкие операции периодически, регулярно или по запросу, и для этих операций важно, например, наличие интернета или чтобы устройство «не спало». Чаще всего при решении подобных задач применяются AlarmManager, WakefulBroadcastReceiver, либо же вообще WakeLock контролируется вручную. Все это не рекомендуется в документации для разработчиков под Android, а WakefulBroadcastReceiver уже отмечен как deprecated с API level 26.0.0.
Что же мы можем сделать, чтобы следовать рекомендациям Google и создавать приложения с более гибким поведением на версиях Android 5.0+, в которых энергосбережению уделяется все больше внимания? Если вы готовы выставить минимальный API level 21.0.0 для своего приложения, предлагаю под катом пример использования JobScheduler в связке с IntentService для последовательного выполнения трудоемких задач.
Суть вопроса и поднятых проблем
Достаточно открыть документацию по классу WakefulBroadcastReceiver, чтобы увидеть интересную ситуацию: класс добавлен в 22.0.0 версии API, а отмечен как deprecated в 26.0.0 версии. Можно предположить, что сначала разработчики Android решили добавить удобный класс для выполнения задач с удержанием WakeLock, но потом оказалось, что никто не гарантирует, что приложение будет работать на переднем плане, да и вообще когда каждое «умное» приложение пытается удержать WakeLock, когда же устройству экономить энергию? Да и принцип работы WakefulBroadcastReceiver стал идти в разрез со стремлением продлить время жизни заряда батареи, ведь кроме всего прочего незакрытый правильным образом ресивер мог привести к утечкам WakeLock‘ов.
С другой стороны у рядовых разработчиков может возникнуть вопрос, как при всех новых ограничениях безопасно выполнять периодические задачи, когда, например, активен Doze mode? Чтобы уравновесить баланс ограничений и возможностей был создан JobScheduler, который берет на себя решение вопросов о том, когда задаче можно выполниться, каким приемлемым образом предоставить ей возможность выполнения, если это действительно очень нужно, и как при этом не нарушить политику энергосбережения и не потерять где-нибудь не отпущенный WakeLock.
Пока готовилась данная статья, на Хабре появилась статья другого автора, в которой раскрыто чуть больше теории и уделено чуть меньше внимания практике. Она будет полезной для быстрого старта и более глубоко понимания существующих альтернатив JobScheduler‘у.
Пример создания проекта с JobScheduler и IntentService
Для простоты примера представим, что у нас есть задача по записи слова «Exercise» в файл, при этом нам нужен интернет, причем желательно не мобильный, потому что наша задача не стоит траты мобильного трафика пользователя. При этом мы хотим, чтобы каждая новая задача по записи слова выполнялась друг за другом. Так как нам нельзя блокировать главный поток (тут и работа с файлами и, возможно, какие-то сетевые запросы), и у нас задачи могут выстраиваться в очередь, на помощь нам приходит IntentService.
Для полного воспроизведения шагов, описанных в данной статье, необходимо создать новый проект без активити, с минимальной версией SDK 21. Также все действия можно производить в уже существующем проекте с минимальной версией SDK не ниже 21.
Добавление IntentService для обработки задач в фоне
Добавляем в проект новый IntentService, называем его ExerciseIntentService, используем стандартный подход, основанный на автоматически сгенерированных методах, почистив лишнее и переименовав методы и константы под наши условия.
В результате несложных манипуляций получаем следующее тело для ExerciseIntentService:
И так, сервис, который будет непосредственно выполнять наши однотипные условно-трудоемкие задачи по порядку, готов. Для того, чтобы создавать и планировать задачи, необходима точка входа, в качестве которой можно использовать BroadcastReceiver.
Добавление BroadcastReceiver для инициализации задач
Добавим в проект стандартными средствами BroadcastReciever и назовем его ExerciseRequestsReceiver. В дальнейшем мы сможем откуда угодно отправлять бродкасты нашему приложению, чтобы оно планировало выполнение задач (например, это можно сделать с помощью инструментальных тестов, что будет показано ближе к концу статьи).
Минимально необходимый код ExerciseRequestsReceiver выглядит так:
String ACTION_PERFORM_EXERCISE — действие для идентификации необходимости запуска процесса планирования задачи.
int sJobId — переменная для идентификатора задачи, который будет использован при планировании задач.
scheduleJob(context) — вызов метода, который будет содержать всю необходимую логику для планирования задачи.
Теперь при получении бродкаста мы могли бы отправить интент нашему ExerciseIntentService, и все бы было хорошо, только в нашем случае необходим WakeLock, а так как мы договорились больше не использовать WakefulBroadcastReceiver, то необходимо создать и запланировать новую задачу для JobScheduler, а дальше он все сделает за нас (почти).
Кстати, при использовании JobScheduler не нужно разрешение на WakeLock, в отличие от других способов решения подобной задачи.
Реализация наследника JobService для обработки событий от JobScheduler
JobScheduler требует отдельный сервис, унаследованный от JobService. Назовем его ExerciseJobService и добавим как обычный сервис, заменив родительский класс и добавив разрешение в манифест модуля:
Разрешение android.permission.BIND_JOB_SERVICE необходимо, чтобы данный сервис смог взаимодействовать с JobScheduler.
Кроме этого обязательными для реализации являются два метода onStartJob() и onStopJob() .
- onStartJob() вызывается когда настает время (условия) для выполнения запланированной задачи. Этот метод вызывается в главном потоке и любые тяжелые операции разработчик должен самостоятельно выносить в отдельные потоки (в нашем случае это уже предусмотрено — мы используем IntentService). При делегации выполнения задачи в другие потоки из onStartJob() необходимо вернуть true, а если все необходимые действия уже выполнены в теле этого метода, то вернуть нужно false.
- onStopJob() вызывается тогда, когда требуемые условия для задачи перестали выполняться либо отведенное для задачи время исчерпано. Вызов этого метода информирует сервис, что все фоновые задачи немедленно должны перестать выполняться. Лучше всего предусмотреть безопасную логику остановки выполнения для обеспечения целостности данных.
Для правильной обработки ситуации с onStopJob() можно реализовать статические флаги, какие-либо дополнительные бродкасты, а также можно использовать другие средства взаимодействия сервисов. В текущей статье это будет опущено и введено допущение, что если наш IntentService не смог выполнить задачу, то ничего страшного для логики приложения и целостности данных не произойдет.
onStopJob также имеет возвращаемое значение, если это true — то JobScheduler поставит прерванную задачу в очередь выполнения снова, false — задача будет считаться выполненной и будет удалена из очереди, если она не была периодической.
Так как обработка внештатных ситуаций опускается, вернем из этого метода true, чтобы задача перезапланировалась, заодно это нам позволит рассмотреть использование критерия повтора.
Таким образом, установив возвращаемые значения из двух основных методов и добавив запуск нашего ExerciseIntentService в onStartJob() , получаем следующий достаточно емкий сервис:
На данный момент уже подготовлен минимальный набор классов для реализации выполнения задачи через JobScheduler в IntentService, а именно: ExerciseIntentService — выполняет непосредственно необходимые для задачи операции в отдельных потоках, ExerciseJobService — ловит события от JobScheduler‘a и запускает ExerciseIntentService, а ExerciseRequestsReceiver — входная точка для работы нашего комплекса, где мы ловим бродкасты извне и должны инициализировать задачу для JobScheduler, чем далее и займемся.
Создание новой задачи для JobScheduler
Для создания задачи для JobScheduler понадобится JobInfo.Builder. Его конструктор принимает два параметра: идентификатор задачи и ComponentName нашего ExerciseJobService.
Идентификатор задачи и ComponentName
С идентификатором все просто (но не без нюансов) — любое целочисленное значение:
- постоянное, если мы хотим обновлять уже запланированную задачу либо же контролировать единственность периодической задачи;
- уникальное значение, если мы хотим создать очередь из отдельных задач.
Если вдруг ваше приложение системное, либо же у вас есть несколько приложений с одним sharedUserId, то нужно учитывать дополнительное условие: id не должен пересекаться среди всех приложений с одним uid. Таким образом, если приложение использует android.uid.system, то нужно учитывать, что некоторые системные задачи также используют JobScheduler, и уникальность id нужно поддерживать самостоятельно.
Кстати, при использовании таких методов у JobScheduler как removeAll() мы можем удалить и чужие задачи с тем же uid.
Статья на английском о том, как можно контролировать подобную ситуацию.
В рассматриваемом примере не нужно забоится об UID и в качестве идентификатора используется инкрементируемое значение sJobId.
sJobId определен следующим образом:
С ComponentName все гораздо проще, это объект в конструктор которого передается ExerciseJobService.class.
Инициализация параметров с помощью JobInfo.Builder
Ниже рассмотрим основной набор методов JobInfo.Builder.
минимальное время, которое пройдет прежде чем задача будет выполнена, другими словами время отложенного старта.
максимальное время, в течение которого задача может находиться в очереди / запланированном состоянии. Если по истечению 5 секунд (в нашем случае) благоприятные условия так и не наступили, задача начнет выполняться ни смотря ни на что (если это не противоречит другим политикам JobScheduler). Если не использовать, тогда задача будет выполнена только при наступлении необходимых условий.
задается тип подключения, например, нам нужен интернет, но чтобы это был свободный WIFi (не hotspot) или Ethernet, тогда мы выбираем NETWORK_TYPE_UNMETERED .
Определяет состояние, когда пользователь не взаимодействует с устройством, в нашем случае это не важно.
В нашем примере предположим, что нам без разницы, заряжается устройство или нет.
Можно установить для задачи критерий повтора (подробнее об этом чуть ниже):
Кроме этого есть возможность сделать задачу периодической:
Периодические задачи
Когда мы устанавливаем задаче периодичность, мы сталкиваемся с логичными ограничениями:
- setMinimumLatency() и setOverrideDeadline() использовать нельзя, так как не имеет смысла — задача так или иначе должна выполниться один раз в течение заданного интервала, и никакие дополнительные ограничения сверху или снизу недопустимы. С другой стороны иногда нам нужно чего-то подождать, а потом начинать периодическую задачу — здесь добавить такое условие нельзя, если нужно ждать, значит ждать нужно до того, как добавлять задачу на выполнение.
- в JobService в onStopJob() нам можно не возвращать true — прерванная периодическая задача не будет удалена из очереди, в следующий раз она выполнится по расписанию.
- никто не гарантирует, что задача будет выполнена ровно через заданный интревал, она просто будет выполнена не более чем 1 раз за этот интервал.
Это основные отличия периодической задачи от обычной. В текущем примере мы не будем делать задачу периодической.
Критерий повтора выполнения задачи
setBackoffCriteria() позволяет задать правило, по которому будет произведена повторная попытка выполнения задачи в случае необходимости (например в onStopJob() мы вернули true).
JobScheduler предлагает нам две политики: линейная и экспоненциальная.
Формула линейной политики следующая:
т.е. от текущего момента времени следующая попытка будет предпринята через заданное количество времени умноженное на количество неудач.
Формула экспоненциальной политики:
Здесь же время следующей попытки растет гораздо большими шагами.
Все достаточно просто и прозрачно, но что будет, если наша задача не может выполниться успешно множество раз, 10, 20. При заданном начальном времени повтора в 1 минуту к 10 попытке пройдет практически час. Отлавливать такие ситуации достаточно не просто, потому что мы не можем предсказать что будет через час. JobScheduler ограничивает такие повтороения пятью часами.
Таким образом с параметром setBackoffCriteria() нужно обращаться очень осторожно, тщательно продумывать начальное время и тип политики в соотвествии с поставленными задачами. Возможно также придется осуществлять дополнительную обработку, например, количества повторов и удалять задачу из JobScheduler.
Отправка задачи на выполнение
Таким образом у нас готов JobBuilder со всеми необходимыми нам параметрами. Для добавления задачи в очередь выполнения необходимо получить у системы инстанс JobScheduler:
И вызвать метод для добавления JobInfo из билдера:
Для надежности можно проверить возвращаемое значение последнего метода, который нам скажет об успешном или не успешном добавлении задачи в очередь.
Далее когда JobScheduler находит, что условия для выполнения задачи оптимальные, вызывается метод onStartJob() в ExerciseJobService, который уже разобран выше.
Проверка и отладка
Если подытожить, то в результате у нас получилось тестовое приложение, которое позволяет планировать одноразовые задачи, которые выполняют условно-тяжелую операцию в IntentService.
Для проверки работоспособности предлагаю добавить небольшой инструментальный тест, который выглядит примерно так:
Также, для проверки работы примера в исходный код были добавлены некоторые логи, которые не указаны выше в выдержках.
Если мы запустим тест, то мы увидим, что наша задача с «id: 1» стартует и завершается, стартует и завершается… Точнее её принудительно завершает JobScheduler.
Выполнение задачи в отдельных потоках и оповещение сервиса о завершении
В данном примере из метода onStartJob() мы вернули true, а это значит, что мы сообщили JobScheduler‘у что выполнение задачи продолжается где-то в побочном потоке. Так как мы не уведомляем о завершении задачи, JobScheduler завершает её принудительно, а так как из onStopJob() мы тоже возвращаем true срабатывает политика повтора, и задача перепланируется и запускается заново.
Чтобы такого не происходило, нужно вызывать метод jobFinished() в классе сервиса ExerciseJobService, о его использовании и различных вариантах передачи информации о завершении задачи из IntentService я постараюсь рассказать в следующих статьях.
На этом создание тестового примера завершается, он готов к использованию и применению в рабочих проектах для планирования задач. Для выполнения задач в фоне здесь был использован IntentService, но допустимы и другие способы, например, использование ThreadPoolExecutor или HandlerThread. А в случае разработки исключительно под Android O и выше, рекомендую также обратить внимание на JobIntentService.
Полный код рассматриваемого примера приведен на GitHub.
Также можно ознакомиться с официальным пример реализации JobScheduler с Activity на developer.android.com.
Иллюстрация: Anni ART (копирование и воспроизведение только с согласия автора).
Источник