В данной статье, речь пойдет об одной проблеме с JobIntentService'ом, о которой существует множество вопросов на соответсвующих ресурсах и репортов в баг трэкере гугла. А так же о причине по которой, судя повсему, гугл не считает ее багом и закрывет данные репорты.


Введение


JobIntentService’ы были созданы для фоновой работы. Широкое применение они получили в Android 8 и выше, когда исчезла возможность использования сервисов в фоновом режиме.
По сути они заменяют сервисы в фоновом режиме, а так же находятся под управлением планировщика задач(JobScheduler).


Таким образом система имеет возможность контролировать ход выполнения задач в фоновом режиме а так же сама управляет wakelock’ами, что позволило оптимизировать расход батареи устройства и избежать не корректного использования wakelock’ов разработчиками. Данные шаги позволили минимизировать ситуации когда устройство не может погрузится в режим сна(Doze Mode), что опять же влияет на экономию заряда батареи.


Коротко о JobIntentService


По сути JobIntentService это тот же IntentService под управлением планировщика задач(JobScheduler).


Выполняется в фоновом потоке AsyncTask’а.


В версиях Андроида 4.4 и ниже используется обычный IntentService.


Подробное описание можно прочитать в документации


Жизненный цикл и подводные камни


Оба типа задач имеют одинаковый жизненный цикл. Задачи контролируются Handler’ом и имеют состояния.


Хотя эти состояния не доступны извне, но при определенных обстоятельствах система выбрасывает исключения, при которых приложение “падает”. Данные поведения являются проблемой и головной болью для многих разработчиков и к сожалению не имеют простого решения. Для начала изучим состояния и жизненный цикл задач, а потом рассмотрим возможные решения.


Последовательность состояний задач


image
BINDING — состояние создания задачи(байндинг сервиса) тайм-аут 18 секунд.
STARTING — состояние запуска задачи, тайм-аут 8 секунд.
EXECUTING — состояние выполнения задачи, тайм-аут 10 минут.
STOPPING — состояние остановки задачи(например после вызова cancel()), тайм-аут 8 секунд.
FINISHED — финальное состояние завершенной задачи, последнее состояние в жизненном цикле задачи.


Упрощенная схема жизненного цикла задач


image
У каждого состояния задачи есть свой тайм-аут. По истечении тайм-аута выполнение задачи прерывается вне зависимости от ее состояния. Собственно это механизм тайм-аутов и является подводным камнем т.к. по истечении тайм-аута система выбрасывает исключение типа java.lang.SecurityException и приложение “падает” со следующим сообщением Caller no longer running, last stopped +1s600ms because: timed out while starting где +1s600ms это время которое прошло с момента тайм-аута до момента выброса исключения, а “причина” (because: timed out while starting) — указывает в каком состоянии была задача, когда тайм-аут истек.


Выводы


Как показывает опыт, данные исключения встречаются в довольно таки загруженных приложениях.


В подтверждение данной проблемы можно наблюдать как и на слабых, так и на топовых устройствах. На данную проблему так же указывает и выбрасываемые исключения с сообщениями о тайм-аутах. Соответственно, само собой напрашивается решение о разгрузке приложения и оптимизации использования JobIntentService’ов, например, избегать ситуаций когда паралельно запускаются несколько JobIntentService’ов. Второе решение, в некоторых случаях более тривиальное, а иногда и сложнее первого варианта — это использовать JobService.


Так же если погуглить данную проблему то можно наткнуться и на другие "сомнительные" варианты решения данной проблемы, например можно посмотреть следующие ссылки:


Вариант 1
Вариант 2
Вариант 3


P. S.


На данный момент гугл готовит хорошую замену JobService’ам и JobIntentService’ам — это Worker и WorkManger из пакета androidx.work.


К сожалению данные инструменты еще не готовы к продакшену и имеют ряд багов, но уже сейчас как показали тесты, решают проблему описаную выше.

Комментарии (0)