- IntentService
- Промежуточные результаты
- Запуск и остановка службы
- Вызываем Toast
- Полный список
- IntentService
- Foreground
- Автозагрузка
- Основы Android: Основы IntentService
- Шаг 0: Начало работы
- Шаг 1: Определение обработки сообщений
- Планирование задач в Android с использованием JobScheduler и IntentService
- Суть вопроса и поднятых проблем
- Пример создания проекта с JobScheduler и IntentService
- Добавление IntentService для обработки задач в фоне
- Добавление BroadcastReceiver для инициализации задач
- Реализация наследника JobService для обработки событий от JobScheduler
- Создание новой задачи для JobScheduler
- Идентификатор задачи и ComponentName
- Инициализация параметров с помощью JobInfo.Builder
- Периодические задачи
- Критерий повтора выполнения задачи
- Отправка задачи на выполнение
- Проверка и отладка
- Выполнение задачи в отдельных потоках и оповещение сервиса о завершении
IntentService
Класс IntentService является подклассом класса Service. Он используется, если надо выполнять какие-то тяжёлые задачи с намерениями, которые могут выполняться асинхронно. Принцип работы этого вида сервиса прост. Он создаёт новый поток для своей работы, затем мониторит все входящие намерения и отправляет их на обработку в этот поток. Далее в коде вы определяете, как обработать Intent. Вам не нужно запускать AsyncTask и управлять тяжёлыми задачами, сервис сам справится. Вы можете отправить данные обратно в приложение через широковещательное сообщение и принять сообщение через широковещательный приёмник.
Иными словами, приложение посылает в сервис данные через метод startService(), в которых передаёт намерения. IntentService принимает эти вызовы, берёт намерения последовательно и отправляет их в очередь на обработку. И далее они поочерёдно обрабатываются в отдельном потоке методом onHandleIntent() по одному за раз. Когда последний Intent из очереди будет обработан, сервис сам завершит свою работу.
Во многих случаях использование IntentService проще, чем AsyncTask или Thread.
Напишем код для основной активности, в которой осуществим три вызова сервиса через метод startService(). Переменная time – это время паузы, которую будем делать в сервисе, а task – просто метка, чтобы отличать вызовы. Для этого создаётся намерение с указанием нужной службы, а в дополнительных параметрах передаются данные.
В коде встречается вызов класса MyIntentService. Создадим его.
В классе необходимо создать конструктор, в котором вызываем конструктор суперкласса и указываем какое-нибудь имя, которое будет использовано для наименования потока.
В методе onHandleIntent() обрабатываем намерения. Достаём из них значения переменных time и task, запускаем паузу на time секунд и выводим в лог значения label в начале и в конце.
Не забываем прописать сервис в манифесте в секции application:
В итоге, при запуске в логах видим следующее:
Сервис создался, вызовы выполнились по очереди и сервис завершил работу.
Усложним задачу. Сейчас мы просто вывели результаты в журнал, а сама активность не получила уведомления. Создадим отдельное намерение и отправим его через метод sendBroadcast().
Теперь в основной активности необходимо зарегистрировать свой BroadcastReceiver и принять сообщение от сервиса:
В данном примере мы возвращаем результат после выполнения всех задач и выводим в одной текстовой метке. В методе onHandleIntent() вы можете выполнять тяжёлые задачи. Так как они выполняются в другом потоке, то ваша основная активность остаётся отзывчивой на действия пользователей. Как только сервис завершит обработку данных, то сработает отправка широковещательного сообщения обратно приложению через метод sendBroadcast(). И вам его надо только принять и обработать.
Не забывайте снимать регистрацию приёмника через метод unregisterReceiver() (рекомендуется в методах onPause() или onDestroy() в нашем примере).
Промежуточные результаты
Дополним программу и будем посылать в активность промежуточные результаты, которые будут обновлять состояние индикатора прогресса. Добавим индикатор на экран.
Добавим в класс службы дополнительный код.
В активности следует зарегистрировать ещё один широковещательный приёмник. Сервис в цикле создаёт промежуточные данные и отсылает его активности. Активность принимает данные через приёмник и обновляет состояние индикатора.
Запустив пример, вы увидите сначала работу индикатора прогресса, а потом выведется текст. Также промежуточные данные можно отсылать в уведомления. Добавим код в существующий код:
Если вы запустите код, то увидите, что будет обновляться не только индикатор на главном экране приложения, но и данные в уведомлениях.
Запуск и остановка службы
В примерах служба запускалась одновременно с запуском приложения. Перенесём код в кнопку. А также добавим вторую кнопку для остановки службы. При этом будем отслеживать количество запущенных служб.
Код для активности.
Вызываем Toast
Вызвать Toast в методе onHandleIntent() напрямую не получится, сообщение не появляется. Следует создать отдельный поток. Чтобы выполнение проходило в основном потоке, объект Handler должен быть создан в методе, выполняемом в основном потоке. Метод onHandleIntent() не подходит, так как он выполняется в фоновом потоке. Вместо этого воспользуемся методом onStartCommand(), который вызывается каждый раз при запуске службы через намерение. Метод onStartCommand() выполняется в основном потоке и отрабатывает до метода onHandleIntent(). Если создать объект Handler в методе onStartCommand(), то его можно будет использовать для передачи кода в основной поток в методе onHandleIntent():
Источник
Полный список
— изучаем 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: Основы IntentService
Сегодня мы хотели бы познакомиться с одним из менее известных, но очень полезных типов услуг, которые вы, возможно, захотите использовать: IntentService.
IntentService ( android.app.IntentService ) — это простой тип службы, который можно использовать для обработки асинхронной работы из основного потока посредством запросов Intent. Каждое намерение добавляется в очередь IntentService и обрабатывается последовательно.
IntentService — это один из самых простых способов разгрузить «куски» обработки из потока пользовательского интерфейса вашего приложения в удаленную рабочую очередь. Нет необходимости запускать AsyncTask и управлять им каждый раз, когда у вас есть больше обработки. Вместо этого вы просто определяете свою собственную службу, упаковываете намерение с соответствующими данными, которые вы хотите отправить для обработки, и запускаете службу. Вы можете отправить данные обратно в приложение, просто отправив результат в виде объекта Intent и используя широковещательный приемник, чтобы перехватить результат и использовать его в приложении.
Шаг 0: Начало работы
Мы предоставили пример приложения, которое иллюстрирует разницу между попыткой выполнить обработку в основном потоке пользовательского интерфейса (нет-нет ответа приложения) и выгрузить эту же обработку в IntentService. Код можно загрузить по ссылке для скачивания кода в верхней части этого руководства.
Шаг 1: Определение обработки сообщений
Во-первых, важно понимать, когда и зачем пользоваться услугами в целом. Хорошая причина использовать IntentService — это когда у вас есть работа, которая должна выполняться вне основного потока, чтобы приложение работало и работало быстро. Другая причина в том, что у вас может быть несколько запросов на обработку, и они должны быть поставлены в очередь и обработаны на лету.
Допустим, у нас есть приложение, которое должно выполнить «обработку». Мы хотели что-то простое, поэтому мы в основном определим обработку в этом случае как получение строкового параметра, выполнение «вещи» и возвращение строкового результата. Чтобы этот урок был простым, мы будем спать 30 секунд, делая вид, что делаем что-то полезное. В действительности, «вещи», вероятно, будут обработкой изображения, подключением к сети или какой-либо другой операцией блокировки.
Следовательно, если бы вся «обработка» происходила в основном приложении, у вас мог бы быть элемент управления EditText для ввода сообщения и элемент управления TextView для разделения результата. Вы можете поместить этот код в обработчик Button для запуска обработки. Код в обработчике нажатия кнопки в классе Activity приложения будет выглядеть примерно так:
Источник
Планирование задач в 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 (копирование и воспроизведение только с согласия автора).
Источник