Современные способы запуска фоновой работы в Android

Современные Android-смартфоны уже давно стали мощными устройствами, способными выполнять множество операций. Вся наша коммуникация стала асинхронной, мы передаем и получаем множество данных. Для всего этого важно гарантировать выполнение работы в фоне, т. е. когда приложение свёрнуто.

В этой статье я разберу актуальные API для выполнения различного рода задач в фоне. Тут очень важно слово «актуальные». На момент выхода этой статьи уже вышел Android 14, поэтому список API и подходы будут актуальны. Да-да, как вы могли узнать из моей статьи про «Ограничения фоновой работы», уже на протяжении многих лет мы получаем новые ограничения — и через пару лет обязательно будут новые, как и новые API. Рекомендую ее прочитать, чтобы лучше понять API, про которые я расскажу в этой статье.

Все, о чем я буду рассказывать в этой статье, касается современного Android 14 и нескольких последних версий. На старых версиях Android API, о которых я расскажу, этого может не быть, либо оно будет работать иначе. Чем старше версия Android, тем и ограничений на запуск работы в фоне тоже будет меньше, поэтому вы сможете использовать альтернативные решения и подходы. Я за следование правилам из новых Android на всех поддерживаемых вашим приложением версиях Android, чтобы добиваться унификации поведения. Но это только мой взгляд, и вы вольны делать то, что вам хочется.

Прежде чем начнете читать этот разбор, я настоятельно рекомендую прочитать статью про историю ограничений выполнения работы в фоне.

История ограничений фоновой работы в Android для разработчиков

Когда-то в Android были времена, когда запустить задачу в фоне было просто и гарантии её работы были…

habr.com

Если вам интересно следить за самыми последними новостями Android разработки и получать подборку интересных статей по этой тематике, тогда вам стоит подписаться на Телеграм-канал Android Broadcast и мой YouTube канал «Android Broadcast»

Классификация фоновой работы

Чтобы правильно выбрать API для выполнения работы в фоне, важно классифицировать вашу задачу. Я вывел несколько критериев, на основе которых делаю выбор:

  • длительность выполнения (короткая или долгая);

  • нужно ли пользователю знать о задаче или управлять ее выполнением;

  • важность выполнения задачи как можно быстрее.

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

Я собрал все вопросы, на основе которых я делаю выбор, и свел их в диаграмму принятия решения, которую вы можете увидеть внизу. Далее я сделаю краткий обзор каждого из API и когда его стоит делать.

Алгоритм выбора API для запуска задачи в фоне

Алгоритм выбора API для запуска задачи в фоне

Специальные API. Download Manager

Загрузка файла в Chrome на основе Download Manager

Загрузка файла в Chrome на основе Download Manager

Для начала быстро пробежимся по специальным API и начнем с самого простого и, как мне кажется, непопулярного, — DownloadManager. Это системный сервис, который обрабатывает загрузку файлов с публичных HTTP/HTTPS адресов. Сервис возьмет на себя всё взаимодействие по HTTP, повтор загрузки после ошибки или когда изменилось соединение. Также во время загрузки можно показать уведомление с прогрессом, чтобы пользователь видел прогресс загрузки и мог им управлять. А можно сделать загрузку без этого, но я вам так не рекомендую делать. Пусть всё будет прозрачно и понятно!

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

Download Manager. Запуск новой загрузки файла

Download Manager. Запуск новой загрузки файла

Download Manager. Получение информации о состоянии загрузки

Download Manager. Получение информации о состоянии загрузки

Download Manager не подойдет для сложных случаев, когда вам надо делать настройку HTTP клиента для подключения к защищенному серверу или сохранить файл вне допустимых папок. Введение Scoped Storage в Android 11 снизило риски похищения файлов и лучше защищает директорию вашего приложения на внешнем хранилище.

Также не забывайте, что всё скачанное из интернета перед использованием нужно проверить на предмет подмены файла. Например, можете зашифровать данные или проверять контрольную сумму скачанного файла.

Я рекомендую вам использовать Download Manager для простой загрузки файлов, чтобы она надежно работала в фоне, независимо от версии Android.

Sync Adapter

Видели в системных настройках Android возможность создавать и управлять аккаунтами приложения? Помимо этого, там также можно выбрать различные данные для синхронизации. Под всем этим скрывается Sync Adapter API. Он позволяет синхронизировать данные между удаленным сервером и вашим устройством, чтобы везде всё было актуально. Например, контакты можно редактировать на телефоне, и они обновятся на сервере, а также на всех устройствах, связанным с аккаунтом, при условии, что включена синхронизация.

621d449c9eb1d0d2bb4456faa7984e00.jpg

Sync Adapter используется крайне редко и в приложениях, которые используют общий аккаунт на несколько сервисов, таких как Google, Яндекс и др. Часть приложений заводят аккаунт только для интеграции с системой. Реализация синхронизации через Sync Adapter является нетривиальной задачей и требует несколько этапов создания аккаунта приложения через системные возможности, а не внутри приложения. Описывать весь процесс я не буду, но он включает в себя работу с AccountManager, Bound Service, ContentProvider, специальными XML по конфигурации, а также реализацию SyncAdapter.

Пример реализации Sync Adapter и взаимодействия с частями системы

Пример реализации Sync Adapter и взаимодействия с частями системы

Это API может вам пригодиться, если на каждом привязанному к аккаунту устройстве надо хранить актуальные данные и при их изменении отправлять их на сервер. Можно также настроить периодическую синхронизацию, но минимальная её частота — 1 час, плюс возможны смещения из-за экономии энергии и других режимов оптимизации расхода батареи. Я думал, что Sync Adapter используется редко, но вот один из подписчиков рассказал мне, что использует его для синхронизации файлов с сервером в фоне. Возможно, это одно из самых надежных решений для синхронизации в фоне, так как я не нашел никаких упоминаний о развитии API или появлении серьезных изменений в его поведении. Либо их не озвучивают, либо, думаю, что API мало востребовано сторонними приложениями.

Service

Давайте двигаться в тему наиболее распространенных API, о которых вы уже слышали, а скорее всего, уже использовали. Обычный вопрос на собеседованиях — стандартные компоненты Android, и я надеюсь, что вы их знаете. Service — это компонент для работы приложения без графического интерфейса. Например, если вам надо выполнить долгую работу в фоне и не отвлекать пользователя, а дать ему посмотреть видос или посидеть в соцсетях. К сожалению, на момент выхода этой статьи использовать обычный Service, он же Background, практически невозможно, т. к. скрытая от пользователя работа приложений не приветствуется системой и убивается через пару секунд после ухода приложения из состояния Foreground. Background Service может полноценно работать только в случае, когда приложение видно пользователю. Чтобы Service остался работать после ухода приложения с экрана, надо успеть трансформировать Background Service в Foreground, на что как раз и дается время после ухода приложения в фон. Стандартных callback-ов для этого нет, так что определения ухода в фон придется писать самостоятельно. На практике мало кто заморачивается такими сложностями с запуском Background Service и отслеживанием состояния приложения, поэтому сразу используют Foreground Service, о котором мы и поговорим дальше, но прежде вспомним про еще один тип Service — Bound.

Bound Service

Bound Service — это взаимодействие по принципу «клиент-сервер», которое позволяет компоненту приложения, например Activity, подключиться к Service и отправлять запросы через вызовы методов, а не коммуникацией с помощью Intent. Основное предназначение этого формата Service — обеспечение взаимодействия между приложениями, которые работают в разных процессах, но это необязательно. Фактически Inter Process Communication или сокращенно IPC — основная цель, с которой создавали Bound Service.

Пример Bound Service, когда клиенты работают в одном процессе с ним

Пример Bound Service, когда клиенты работают в одном процессе с ним

Подключение к Bound Service

Подключение к Bound Service

Разъяснение того, как организовать межпроцессную коммуникацию (Interprocess Communication) через Bound Service, выходит за рамки этой статьи, но обычно это используют разработчики библиотек. Например, Google Play Billing.

В современных версиях Android жизнь Bound Service ограничена как у Background Service, но Bound Service привязан к жизни компонента, который выполнил binding этого Service. Bound Service используется для задач передачи данных между приложениями и для выполнения долгой работы он вам не подойдет, а вот Foreground Service уже может намного больше!

Foreground Service

Foreground Service отличается от обычного Service тем, что он виден пользователю через показ уведомления в системной панели и его привязке к Service. Также Foreground Service повышает гарантии, что приложение не будет убито, по сравнению с Background Service.

Foreground Service стоит использовать для долгих, либо высокоприоритетных задач, которые не могут быть отложены, а пользователю надо показать информацию о выполнении, например, прогресс, а также дать возможность управлять работой через кнопки в уведомлениях. Под такие типы задач попадает проигрывание медиа, запись разговора, процессинг данных в фоне, загрузка данных с или на сервер и др.

На момент выхода этой статьи Foreground Service имеют типы операций, для которых они могут запускаться. А в Android 14 указание типа стало обязательным, и Google Play будет контролировать действительную необходимость использовать Foreground Service для задач вашего приложения. Довольно подробно я рассказал об этом в статье про изменения в Android 14.

Разбор Android 14 для разработчиков

Android 14 уже здесь, поэтому я взялся за документацию, разбор экспертов и другие доступные ресурсы,…

habr.com

Для запуска Foreground Service надо использовать метод Context.startForegroundService () , который запускает Background Service, но ждет, что в течение 5 секунд вы сделаете его Foreground вызовом Service.startForeground (), указав уведомление, которое будет показываться в панели уведомлений.

Запуск Foreground Service

Запуск Foreground Service

Если это не сделать, будет крэш приложения. Начиная с Android 12, запуск Foreground Service из фона невозможен, за исключением специальных ролей приложения или когда это будет вызвано действием пользователя. Фактически их запуск теперь должен происходить, когда приложение видно пользователю.

Если ваша задача не должна запуститься прямо сейчас, а пользователю не нужно ей управлять, то тут мы переходим к основному API для работы в фоне — WorkManager.

WorkManager/JobScheduler

Каждый Service приложения запускается без общего понимания нагрузки на систему, учитывая только объем свободной оперативной памяти. Чтобы централизовать запуск любой фоновой работы, в Android 5 появился новый системный сервис JobScheduler, который запускает задачи на основе нагрузки на систему. Для приложения это осталось запуском специального Service в момент, когда решит система.

Пример JobScheduler Service

Пример JobScheduler Service

Помимо этого, JobScheduler сразу же имел возможность запуска работы при выполнении условий: наличие интернета, достаточное количество заряда батареи и других. Список условий постепенно расширялся. JobScheduler практически не используется напрямую, так как Google рекомендует использовать Jetpack WorkManager.

Единственным исключением для использования JobScheduler я вижу только те случаи, когда там есть возможности, которые еще не портировали в WorkManager, например, User Initiated Data Transfer Job из Android 14, но на замену можно взять Foreground режим выполнения в WorkManager. Для подробной информации про User Initiated Data Transfer Job вам стоит прочитать статью с разбором нововведений Android 14.

Я же сосредоточусь на рассказе про WorkManager. Небольшое интро для тех, кто не в курсе. WorkManager — это Jetpack библиотека, которая позволяет выполнять задачи в фоне, запускать их отложено, либо при выполнении требований к состоянию устройства. Также можно управлять очередью из задач и перезапускать их, даже после перезагрузки устройства. API работает на основе Alarm Manager и Service на Android 4.0 и выше. Начиная с Android 6.0, API перешел на реализацию на основе JobScheduler из Android SDK. WorkManager является аналогом JobScheduler, копируя его в возможностях и расширяя их.

Базовой функцией WorkManager является запустить работу, которую вам надо выполнить, задав необходимые условия для старта. По окончании работы вам надо будет вернуть результат об успешном или неуспешном выполнении. Помимо информации для разработчика, это позволит также запустить задачу повторно позже, что задается политикой при создании работы.

WorkManager. Реализация Worker для выполнения задачи

WorkManager. Реализация Worker для выполнения задачи

WorkManager. Запуск простой загрузки данных на сервер

WorkManager. Запуск простой загрузки данных на сервер

Важно помнить, что WorkManager имеет как и свои ограничения, так и ограничения API, на основе которых работает выполнение задач, а именно:

  • Система сама решает, когда запустить задачу, как много их может одновременно выполняться и сколько давать времени на выполнение в зависимости от версии Android, а также настроек вендора.

  • Запускать задачу повторно нельзя чаще, чем раз в 15 минут.

  • Задача не может выполняться долго, максимум 10 минут на выполнение.

Из-за этих ограничений появились специальные возможности по выполнению работы через WorkManager. Для запуска долгих задач есть возможность работы в Foreground Service или на основе Expedited JobScheduler, начиная с Android 12. При запуске работы вы должны вызвать метод setForeground () и передать туда объекты ForegroundInfo, которые содержит необходимую информацию для запуска Foreground Service ().

WorkManager. Worker запуск задачи в Foreground

WorkManager. Worker запуск задачи в Foreground

Помимо длительного выполнения, есть возможность запуска работы с повышенной гарантией запуска. Система не дает абсолютных гарантий для запуска Expedited Job и времени выполнения, но они выше, чем у обычной job. Важно, что таким образом должны помечаться задачи, очень важные для пользователя, иначе это негативно скажется на опыте.

WorkManager. Запуск важной работы с повышенной гарантией для выполнения

WorkManager. Запуск важной работы с повышенной гарантией для выполнения

WorkManager удобен для выполнения задач, объединяя в себе возможности для запуска работы через JobScheduler, Foreground Services и учитывая ограничения и фичи на разных версия ОС. Это универсальное API, подходящее для выполнения большинства задач, которые имеют четкий результат. Очень мало случаев, когда через него вы не сможете выполнить задачу в фоне. Важно помнить, что WorkManager не выполняет работу бесконечно. На момент выхода статьи максимальное время для работы — 10 минут, но оно может разниться в различных версиях Android.

Service, WorkManager и другие API, о которых я рассказал, не могут одного — запуститься в точное время, а порой это критично. Для этого у нас есть AlarmManager.

Выполнение задачи в точное время

AlarmManager — это системный сервис, позволяющий установить напоминание для мгновенного запуска в будущем. Начиная с Android 4.4, AlarmManager не гарантирует срабатывания в заданное время. API у AlarmManager запутанно, есть метод set () для установки простого будильника, который система может отложить до более удачного времени. Помимо этого, есть метод setAndAllowWhileIdle (), который разрешит сработать будильнику в Doze Mode, т. е. когда устройство переходит в спящий режим и экономит расход заряда батареи.

AlarmManager. Установка будильника

AlarmManager. Установка будильника

Есть методы setExact (), которые сообщают системе, что переносить будильник не стоит, так как это важно. Однако он также может сработать позже.

AlarmManager. Установка будильника без откладывания системой

AlarmManager. Установка будильника без откладывания системой

Также есть метод setWindow (), который аналогичен методу set (), но дает указать длину интервала, в рамках которого должен сработать будильник, но опять же, он может сработать и за его пределами, если у системы не будет возможности вызвать его в заданном окне.

AlarmManager. Установка будильника в заданном интервале

AlarmManager. Установка будильника в заданном интервале

Не менее интересная ситуация с повторяющимися будильниками. Есть метод setRepeating () и setInexactRepeating () — оба без гарантий точного срабатывания. Они могут легко отложить срабатывание будильника за минуту до следующего, хотя желаемый интервал был один час.

AlarmManager. Установка повторяющегося будильника

AlarmManager. Установка повторяющегося будильника

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

AlarmManager. Установка будильника с пробуждением устройства

AlarmManager. Установка будильника с пробуждением устройства

Объясняем пользователю зачем выдать разрешение через настройки

Объясняем пользователю зачем выдать разрешение через настройки

Почему для API, которое должно быть точным, сделали такие ограничения в работе? Все это связано с тем, чтобы разработчики не имели возможности будить устройство, сколько им угодно, тем самым расходуя батарею, а то и вовсе делая что-то плохое в фоне. Для запуска будильников очень не хватает API, где пользователь сможет дать приложению гарантии для срабатывания будильников. Это появилось в Android 12. Для использования методов setExact () и setAlarmClock () надо получать разрешение пользователя SCHEDULE_EXACT_ALARM, а позже появилось еще одно разрешение USE_EXACT_ALARM.

Настройка доступа приложений к заданию точных будильников

Настройка доступа приложений к заданию точных будильников

При загрузке приложений в Google Play вам надо будет доказать модерации магазина, что вашему приложению нужно разрешение USE_EXACT_ALARM, так как оно по умолчанию выдается при установке приложения, а вот SCHEDULE_EXACT_ALARM может получить любое приложение, но вам надо будет убедиться, что пользователь выдал это разрешение. Он может сделать это через системные настройки, поэтому перед открытием системных настроек рекомендуется показать пользователю пояснение, зачем нужно это разрешение.

AlarmManager. Попросить пользователя выдать права на использование точных будильников

AlarmManager. Попросить пользователя выдать права на использование точных будильников

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

Отключение оптимизаций в фоне

Почему так много способов запуска и настроек времени выполнения задач? Связано это с тем, что бездумный запуск разработчиками работы в фоне приводит к чрезмерному расходу батареи, зачастую полностью игнорируя потребности пользователя. Начиная с Android 6.0, началась серия изменений в работе Android и возможностей разработчиков, которые были призваны к стандартизации подходов, и запуск фоновой работы по оптимальным сценариям, по мнению системы. Например, в Android 12 нельзя запустить Foreground Service из фона, за исключением отдельных случаев, в которые попадают важные для пользователя сценарии, приложения с особыми ролями, а также точные будильники. Кстати, об истории ограничений есть статья.

История ограничений фоновой работы в Android для разработчиков

Когда-то в Android были времена, когда запустить задачу в фоне было просто и гарантии её работы были…

habr.com

Скажу сразу, что паники нагонять не стоит, так как все важные сценарии для пользователя остаются работать, и зачастую вы не увидите проблем. Самое важное, что вы должны знать — используйте API, предназначенные для вашей задачи. Стандартная рекомендация — WorkManager, который позволит запустить работу в фоне на пользовательской версии Android.

Не нужно думать, что Google изменил правила безвозвратно. Разработчикам оставили возможность отключить все оптимизации системы для приложения, которые связаны с оптимизацией расхода батареи и ограничением работы в фоне. Но все так так просто…

Системные настройки оптимизации энергопотребления в Android 14

Системные настройки оптимизации энергопотребления в Android 14

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

Единственный вариант попросить пользователя отключить все оптимизации для приложения — запустить стандартный Intent, который появился в Android 6.0, и надеяться, чтобы пользователь это сделал. Тогда приложение может начать лучше работать в фоне, но результат никто не гарантирует.

Запуск системных настроек по отключению оптимизаций энергопотребления для приложений

Запуск системных настроек по отключению оптимизаций энергопотребления для приложений

Android 13+. Настройка расхода заряда батареи приложениями

Android 13+. Настройка расхода заряда батареи приложениями

Помимо этого, в Android 13 появилась настройка расхода заряда батареи для каждого приложения. По умолчанию происходит оптимизация для всех сторонних приложений, а пользователь может выбрать настройку «Ограничено» или «Без ограничений», которые, соответственно, практически запретят работать в фоне или дадут очень много дополнительных возможностям приложений. Как запустить этот экран напрямую, я не нашел — только отправить пользователя в настройки приложения и сказать, какой параметр ему надо открыть.

Вроде бы все просто и понятно, но когда доходит дело до устройств сторонних производителей, начинается зоопарк решений. Каждый производитель пытается выделиться среди других Android-устройств и придумывает свои собственные улучшения и оптимизации расхода заряда батареи, ограничивая приложения. С разработчиками популярных приложений есть договоренности, и на уровне прошивки устройства для такого софта не будет агрессивной оптимизации. Если вы не крупный производитель или у вас нет контактов с производителями, готовьтесь выживать, так как ради того, чтобы продать устройство, производители готовы убивать все процессы, что не попадают в список исключений.

Если вы хотите узнать подробнее про особенности различных вендоров и какие оптимизации применяются, то посетите сайт Don«t kill my app. На сайте вы узнаете, где найти стандартные настройки, связанные с оптимизацией расхода батареи, так и те, что придумал вендор сам. Все представлено в виде скриншотов для разных версий оболочки. Это будет полезно для разработчиков, тестировщиков и поддержки.

Заключение

Самый главный вопрос, который остался нераскрытым — как выбрать API для запуска моей задачи в фоне? Я постарался собрать все известные API на момент, когда последней версией ОС является Android 14, и пришел к схеме, которую вы можете видите ниже. В итоге я понял одно — Foreground Service — в отдельных случаях и WorkManager — для всех других. Нужно больше — объясните пользователю, зачем, и просите отключить оптимизации системы для вашего приложения.

Алгоритм выбора API для запуска задачи в фоне

Алгоритм выбора API для запуска задачи в фоне

Вы можете сказать, что система слишком жестко закручивает болты и не дает жизни разработчикам. Возможно, мы так говорим, потому что с первых версий Android разработчикам давали делать все, что угодно без ограничений, а когда начинают что-то забирать, то сразу будут недовольные. Например, iOS вообще имеет минимум возможностей по работе приложений в фоне, но продажи iPhone и популярность iOS растут, что говорит само за себя.

На мой взгляд, ограничение выполнения задач в фоне для приложений и оптимизация работы в фоне — правильный шаг. Особенно учитывая то, что разработчикам предоставляют много API для работы в фоне, а в крайнем случае пользователь может дать приложению полную свободу. Тенденция использования Foreground Service мне нравится тем, что я как пользователь знаю, что приложение что-то делает в фоне, а на любые операции за рамками этого я должен выдать разрешения. Конечно, эти правила полностью не касаются системных приложений, но тут остается только принимать условия.

Все ограничения на запуск фоновой работы, которые мы имеем сейчас в Android, появились из-за нас. Мы слишком сильно злоупотребляли возможностями, и пришлось вводить регулирование на уровне системы и магазина Google Play, чтобы устройство пользователя и другие приложения на нем могли нормально работать.

Это все, что я хотел вам рассказать про современную фоновую работу. Возможно, что вы читаете эту статью, когда уже вышел Android 15 или новее, тогда не все части будут актуальны, и стоит ждать новых правил.

Делитесь своим мнением касательно API для фоновой работы, сложностях, с которыми столкнулись на устройствах, и других особенностях, которые я не рассказал в статье.

© Habrahabr.ru