Работа в фоне: iOS, Android, Flutter

Рано или поздно перед каждым мобильным разработчиком встает задача выполнить некоторую работу в фоне. Это может быть периодическая предварительная загрузка свежих материалов с сервера, синхронизация данных,  оптимизация работы локальной базы данных, необходимость завершить работу, начатую пользователем, взаимодействие с внешними аксессуарами, отслеживание геопозиции и многое другое. Как говорится, была бы возможность, а уж как ей воспользоваться, придумать не проблема. Если говорить о работе в фоне на десктоп платформах, то очевидно, что никаких проблем с этим там нет, разве что глаза разбегаются от разных возможностей реализации: начиная с запуска процесса в безоконном режиме, заканчивая реализацией служб или даже, при острой необходимости, драйверов. Одним словом, раздолье. В то же время мобильные платформы не могут похвастаться таким разнообразием. Оно и понятно: мобильность накладывает жесткое ограничение (емкость современных батарей, при учете прожорливости ОС и остального софта, оставляет желать лучшего).

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

iOS

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

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

Стоит сразу отметить, что любое взаимодействие в фоне требует соответствующих прав. Это — так называемые режимы работы в фоне (Background modes). Рассмотрим основные режимы работы iOS приложения в фоне:

  • Воспроизведение аудио, аудио-коммуникации посредством AirPlay; режим воспроизведения видео картинка в картинке (Picture in Picture video);
  • гео-позиционные сервисы;
  • IP голосовая связь (Voice over IP);
  • Взаимодействие с внешними аксессуарами (например, Apple Watch);
  • Взаимодействие и общение с внешними устройствами и аксессуарами посредством Bluetooth;
  • Пушоповещения (Apple Push Notification service – APNs);
  • Обновление данных приложения в фоне (Background App Refresh);
  • Фоновые задачи;

Таким образом, имеется не так и много доступных вариантов. А если еще учесть ограничения (о которых чуть позже) конкретных вариантов, то для хоть сколько-то продолжительной работы в фоне может возникнуть соблазн использовать какой-то из режимов не по назначению.  Например: мимикрировать под сервис геопозиции, попутно делая что-то полезное. Но это, само собой, чревато тем что шанс пройти модерацию в AppStore равен практически нулю. И даже если вам это удастся, то на следующем же обновлении вас обязательно «зарубят».

Давайте теперь более подробно рассмотрим ограничение легальных  способов работы в фоне.

Фоновые пуш оповещения

Фоновые пуш оповещения, как должно быть понятно из названия, — это оповещения, которые приходят в фоне. Такого рода оповещения позволяют «разбудить» приложение и выполнить обновление некоторых данных в фоне. В отличие от обычных оповещений, фоновые не показывают алертов, не воспроизводят звуки и не отображают счетчик приложения (badge). Таким образом, для пользователя они невидимы. Фоновые пуш оповещения доступны начиная с iOS версии 7+. Фоновые пуш оповещения имеют ряд ограничений, а именно:

  • Имеют самый низкий приоритет
  • Как следствие предыдущего пункта: не предоставляют гарантий доставки,
  • Доставка осуществляется нечасто и нереуляно
  • Нельзя разослать слишком много оовещений, а именно: не более 2-3 в час. Оповещения сверх этого числа доставлены скорее всего не будут.

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

 

beginBackgroundTaskWithExpirationHandler

Метод, как должно быть понятно из его названия, позволяет заверить выполнение некоторой работы в фоне, которая была запущена пользователем во время работы приложения. На завершение работы система выделяет дополнительное время, которое сильно отличается в зависимости от версии ОС. А именно: на iOS до 7 версии оно ограничено десять минутами сверху. Начиная с iOS версии 7+ это ограничение урезано до трех минут. Таким образом, выполнить что-то затратное по времени с использованием этого метода не получится. Также стоит упомянуть, что дополнительное время выделяется только для задач обернутых в beginBackgroundTaskWithExpirationHandler, а не для любого кода. Другими словами, чтобы важная задача могла заверить свое выполнение в фоне после завершения работы приложения, необходимо явно позаботиться об этом и обернуть код задачи в вызов метода beginBackgroundTaskWithExpirationHandler.

 

Обновление приложения в фоне

Возможность обновить данные приложения в фоне стала доступна, начиная с версии iOS 7+.  Основное назначение этой фичи — дать возможность актуализировать данные приложения перед запуском приложения пользователем. Процесс запуска обновления не детерменирован и определяется системой. Таким образом, повлиять на частоту или время запуска обновления нет никакой возможности. Система сама анализирует то, как часто и когда  пользователь запускает приложение, и незадолго до этого позволяет запросить актуальные данные с сервера. Время выполнения, само собой, ограничено.

 

Фоновые задачи

Фоновые задачи — это новая возможность платформы IOS для выполнения работы в фоне. К этой работе можно отнести:

  • Завершение работы, начатой пользователем во время работы приложения
  • Синхронизация данных
  • Скачивание и загрузка файлов
  • Навигация
  • Интеграция с Siri
  • Периодическое обновление контента, и многое другое.

Таким образом, фоновые задачи — это основная на данный момент возможность работы приложения в фоне. Данная функция доступна на устройствах с iOS 13+. Несмотря на то, что фоновые задачи — это работа в фоне в чистом виде, имеются и ограничения. В частности, запуск задач не детерминирован и определяется системой.

Можно выделить два вида задач:

  • Задачи обновления приложения
  • Фоновые задачи

Фактически задачи обновления приложения — это просто новый API, позволяющий поддерживать данные приложения в актуальном виде. При этом ограничение времени выполнения осталось прежним, а именно не более 30 секунд. Не густо, но для обновления контента, по идее, должно быть достаточно (хотя, конечно, это сильно зависит от контента). Время и частота запуска подобных задач строятся системой на основе паттернов взаимодействия пользователя с вашим приложением. Здесь, вроде бы, ничего нового.

 

Фоновые задачи

Непосредственно фоновые задачи можно использовать, например, для очистки локальной базы данных; для обучения модели ML; синхронизации приложения и т.д., и т.п. Несмотря на то, что временной барьер для подобных задач расширен, он не может превысить несколько минут. Это вроде бы и не плохо (относительно того, что было до появления фоновых задач), но хотелось бы большего.

 

Android

Дела с фоновой работой в андроид обстоят намного лучше, чем на платформе iOS, хотя некоторые ограничения и имеются. На платформе андроид мы можем запускать фоновые задачи, которые могут выполняться с заданной периодичностью или в какое-то определенное время, по пуш оповещению и еще много как. И если на iOS приходится искать хоть какой-то способ выполнить работу в фоне, то на андроид проблема носит несколько иной характер: нужно выбрать подходящий API. А на андроид есть из чего выбрать, т.к. за время развития этой платформы, можно сказать,  образовался целый зоопарк разных технологий:

  • Alarm Manager
  • JobService
  • Firebase Job Dispatcher
  • Backgroud / Foreground Service
  • WorkManager
  • и другие…

Большинство задач можно отнести к одной и трех групп:

  • сиюминтные (задачи, которые необходимо выыполнить прямо сейчас);
  • отложенные (задачи, которые можно выполнить, спустя какое-то время);
  • точные (задачи, которые необходимо выполнить в определенное время).

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

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

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

Сиюминутные задачи

Для выполнения сиюминтных задач можно использовать корутины, если вы разрабатываете свое приложение на языке котлин, или Java потоки, если разработка приложения ведется на языке Java. Соответственно, так же можно воспользоваться WorkManager-ом или Foreground сервисами.

 

Отложенные задачи

Наиболее подходящим API для гарантированного выполенния отложенных задач на текущий момент является WorkManager. Вся грязная и однообразная работа по возобнавлению выполнения задачи в случае ее прерывания и определения наиболее подходящего времени для запуска выполнения работы уже реализовано в WorkManager-е.

 

Точные Задачи

Наиболее подходящим API для выполенния точных задач является AlarmManagr.

 

WorkManager

Чуть более подробно рассмотрим основные возможности WorkManager, т.к. в большинстве случаев необходимую фоновую работу можно выполнить с его помощью. WorkManagеr — это новый API, реализованный поверх ранее доступного API, который позволяет реализовать работу в фоне быстро, удобно, единообразно (используя один и тот же API), и, самое главное, надежно. WorkManagr гарантирует, что задача будет выполнена.

WorkManager реализует свою работу посредством разных сервисов  в зависимости от наличия гугл сервисов на устройстве и версии ОС (см. схему).

 

API WorkManager-а позволяет задать ограничения на то, при каких условиях должна выполняться работа. Например, можно создать задачу, которая запустится только при наличии wi-fi соединения, или если пользователь бездействует с устройством, или при наличии свободного места на устройстве. Есть и целый ряд других ограничений. При этом всю работу по отслеживанию этих ограничений WorkManagеr, что приятно, берет на себя.

 

Можно выделить два вида задач:

  • одноразовые задачи
  • реглярно повторяющиеся задачи.

Думаем, здесь все понятно и дополнительных пояснений не требуется.

 

Другой полезной возможностью WorkManagеr-а является возможность задать политику повторного выполнения задачи в случае, если ее выполнение было прервано или выполнить задачу в данный момент по тем или иным причинам не удалось. Поддерживается два режима: линейный и экспотенциальный. Повторная попытка выполнения задачи в первом случае произойдет ровно через заданный промежток времени. Во втором же случае кажая последующая попытка выполнения задачи будет происходить позже, чем предыдущая.

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

 

Время выполнения фоновых задач ограничено десятью минутами. Как же быть, если необходимо выполнить какую-то ресурсо-, а, следовательно, и временноемкую задачу? Для таких случаев в WorkManager-е есть возможность запустить так называемую Long-running задачу. В таком случае задача будет выполняться как Foreground сервис. Для запуска подобной задачи необходимым условием является отображение оповещения.

 

Flutter

Если вы разрабатываете кроссплатформенное приложение на Flutter и вам необходимо выполнить некоторую работу в фоне, то, возможно, вы задавались вопросом: какие средства для этого Flutter предоставляет?

 

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

Для решения этой проблемы мы можем воспользоваться следующими компонентами, которые нам предоставляет флаттер:

  • Flutter Engine
  • Isolates
  • MessageChannel/EventChannel

Flutter engine – это собственно движок флаттера, который отвечает за выполнение и отрисовку флаттер-приложений на мобильных платформах. С недавнего времени появилась возможность запустить Flutter Engine в так называемом Headless режиме. т.е. в режиме без отображения и отрисовки экрана. Таким образом, мы можем запустить Flutter Engine в фоновом потоке и выполнять дарт код. Flutter Engine позволяет указать альтернативную точку входа приложения, благодаря чему можно легко отделить код основного приложения от кода приложения, работающего в фоне. В некоторых случаях одной лишь альтернативной точки входа может быть недостаточно. Как раз для таких случаев Flutter Engine поддерживает возможность вызова колбеков. Нельзя не упомянуть, что никаких гарантий по поводу производительности двух и более одновременно запущенных Flutter Engine не дается. Этот момент стоит учитывать.

 

С помощью Flutter Engine мы можем запустить дарт код в фоне, но, как уже упоминалось выше, т.к. Flutter/Dart не предоставляют каких-то дополнительных средств для работы в фоне, нам будет необходимо обратиться за помощью к нативной части приложения, а именно к Java/Kotlin и Objective C/Swift. Для осуществления этой возможности дарт предоставляет класс MessageChannel и обертку в виде потока сообщений – EventChannel. Взаимодействие, в данном случае, ничем от взаимодействия с нативной платформой в остальных случаях (плагины, и т.п.) не отличается. Таким образом, особых проблем для взаимодействия с нативной частью нет.

 

Таким образом, основное приложение будет выполняться на дарте, и работа в фоне также будет выполняться на дарте. Довольно часто возникает необходимость взаимодействия между основной частью приложения и фоновой. Конечно, в таком случае мы также можем воспользоваться MessageChannel/EventChannel. Но цепочка взаимодействия будет, мягко говоря, длинной: основной код приложения на дарте через MessageChannel будет обращаться к нативной части приложения, которая с помощью нативных механизмов взаимодейстия сможет обратиться к нативной части фонового процесса. А далее, как легко понять, опять обращаемся из нативной части фонового процесса к дарт части фонового процесса с помощью все того же MessageChannel. Дорога обратно примерно такая же. При этом нужно учитывать, что всю эту нативную реализацию необходимо будет повторить и для второй платформы. Выглядит как довольно много дополнительной работы.

Легко задаться вопросом: а нет ли возможности общения одной части дарт приложения с другой частью, минуя общение с нативной частью приложения? И да, такая возможность есть. Название ей – Isolates, т.е. изоляты. Любой дарт процесс запускается в своем изоляте. А для взаимодействия между изолятами осуществляется также с помощью сообщений и классов ReceivedPort и SendPort. Таким образом, при желании и небольшом количестве кода можно реализовать работу в фоне на языке дарт.

 

Заключение

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

 

Добавить комментарий