Нажимая кнопку «Отправить», вы принимаете правилами обработки персональных данных
Заявка отправлена

Спасибо за проявленный интерес к нашей компании, специалист свяжется с вами в ближайшее время

Услуги
Веб-разработка
Разработка мобильных приложений
Автоматизация бизнеса
UX/UI дизайн
Техподдержка интернет-проектов 24/7 по SLA
Digital-продвижение
Обработка данных
Наша работа
Кейсы
Нажимая кнопку «Отправить», вы принимаете правилами обработки персональных данных
Заявка отправлена

Спасибо за проявленный интерес к нашей компании, специалист свяжется с вами в ближайшее время

Эффективный бэкграунд: организация стабильной фоновой работы в связке двух мобильных медтех приложений

< Все публикации
февраль 2025 ~ 13 минИсточник Habr
Антон Насонов

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

Введение

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

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

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

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

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

Выбор технологического стека

В требованиях заказчика изначально был выбран основной технологический стек – фреймворк разработки мобильных приложений Xamarin на базе .NET. Этот выбор был обусловлен наличием экспертизы и уже написанных ранее приложений на Xamarin и .NET у команды заказчика.

Несмотря на то, что с 1 мая 2024 года “классический” Xamarin больше не поддерживается и не обновляется корпорацией Майкрософт, мобильная разработка на платформе .NET по-прежнему доступна. В настоящий момент Xamarin окончательно интегрировался в платформу .NET и по факту превратился в .NET for iOS и .NET for Android. Также развивается кроссплатформенная разработка .NET MAUI – эволюция Xamarin.Forms.

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

Получение данных из основного приложения

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

1. Межпроцессное взаимодействие (IPC)

  • IPC (Inter Process Communication) позволяет приложениям обмениваться данными и управлять ресурсами.

2. Контент провайдер (Content Provider)

  • Позволяет приложению предоставлять другим приложениям доступ к его данным через абстрактный интерфейс.
  • Пример: приложение “Контакты” в Android, предоставляющее доступ к контактам пользователя другим приложениям, например, таким как банковские.

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

Требования к реализации:

  • Частое обращение к данным. Нам необходимо часто обращаться к данным.
  • Скорость и асинхронность. Запрос должен быть быстрым и асинхронным.
  • Объем данных. Возможность выборки из большого объема данных.

В конце концов мы пришли к варианту с использованием двумя приложениями единого источника данных – локальной базы данных SQLite.

При таком подходе базу данных нам пришлось расположить не во внутреннем хранилище основного приложения, а во внешнем, чтобы обеспечить доступ к ней из приложения-компаньона. Так как база данных доступна во внешнем хранилище, для обеспечения безопасности данных, мы решили ее зашифровать. Для этого мы использовали библиотеку Sqlite-net-sqlcipher, которая позволяет создавать подключения к зашифрованной базе данных.

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

1.png

По умолчанию приложение имеет доступ только к своему внутреннему хранилищу, например, по пути «data/user/0/имя пакета». Для доступа к внешнему хранилищу нужно запросить разрешения.

На главном экране приложения переопределяем метод ViewAppeared

2.png

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

3.png

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

Переопределяем метод OnCreate, наследуемый от MvxActivity

4.png

Проверяем версию Android API и выполняем запрос разрешений на получение доступа

5.png

Следующий код подходит для версий Android API, начиная с 30

6.png

Переопределение метода OnActivityResult позволяет нам отследить, предоставлены ли разрешения и незаметно для пользователя закрыть экран StartupView

7.png

Для создания защищенного подключения используем разные перегрузки конструктора SQLiteConnectionString

8.png

Стоит отметить что одновременные обращения к базе данных SQLite из нескольких разных приложений (процессов) на чтение допустимы. Однако, во время обращения на запись база блокируется и доступна только одному обратившемуся процессу.

Перезапуск сервиса синхронизации данных в основном приложении с помощью push-уведомления в приложении-компаньоне

В нашем проекте push-уведомления выполняют две задачи:

  1. Информационные пуши.
  2. Сервисные пуши. Выполняют задачу перезапуска фонового сервиса основного приложения для синхронизации данных с медицинским устройством.

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

Запуск по пушу без явных действий со стороны пользователя стороннего сервиса, реализованного и объявленного в другом Android приложении – это интересная задача, которая потребовала некоторого ресерча и попутного решения возникающих проблем. Одна из проблем заключалась в том, что на современных, актуальных версиях Android нельзя отправить Intent для запуска сервиса из фона. При попытке такого запуска возникает ошибка. Решение заключается в том, чтобы отправлять Intent из состояния Foreground, то есть из какого-либо запущенного Activity приложения. А для того, чтобы со стороны пользователя этот процесс прошел незаметно, мы запускаем полностью прозрачное активити поверх других приложений и отправляем Intent на перезапуск сервиса из его метода жизненного цикла OnResume.

9.png

Чтобы визуально запуск активити был незаметным для пользователя сделаем его прозрачным:

10.png

А в методе OnResume отправляем Intent на перезапуск (остановку и старт) фонового сервиса с указанием идентификатора (package) основного приложения “com.mainapp” и кастомного действия (action) “com.mainapp.START_FOREGROUND_SERVICE”:

11.png

Чтобы интент отправился, в AndroidManifest приложения-компаньона добавляем элемент <queries> с явным указанием идентификатора (package) основного приложения:

12.png

Иначе при попытке отправки будем получать вот такую ошибку:

W/ActivityManager( 1454): Unable to start service Intent { act=com.mainapp.START_FOREGROUND_SERVICE pkg=com.mainapp } U=0: not found

В AndroidManifest основного приложения добавляем информацию о фоновом foreground сервисе с указанием типа сервиса (connectedDevice) и кастомного действия (action), которое он будет обрабатывать:

13.png

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

14.png

Настройка фоновых задач

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

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

Для этой задачи подходят несколько типов сервисов:

  1. Service – используется для выполнения долгосрочных фоновых задач, которые требуют постоянной работы.
  2. IntentService – сервис, который запускается при получении Intent. После завершения задачи он автоматически останавливается.
  3. JobService — сервис, для которого можно запланировать периодический запуск.
  4. ForegroundService – выполняется в фоновом режиме, при этом отображая постоянное уведомление. Преимущество этого сервиса в том, что он продолжает работать даже при ограничениях на фоновые задачи, введенных еще в Android 8.0 (Oreo) и выше. Начиная с Android 10, также были введены типы foreground сервисов, которые классифицируют задачи, выполняемые в фоне (например, синхронизация данных или воспроизведение мультимедиа). В основном приложении мы используем ForegroundService для обмена данными с подключенными по Bluetooth устройствами.

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

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

В AndroidManifest добавим информацию о сервисе:

15.png

Теперь создадим класс в проекте:

16.png

android.permission.BIND_JOB_SERVICE – необходимое разрешение для запуска сервиса через JobScheduler.

ForegroundService.TypeDataSync – данный тип указывает, что сервис выполняет задачи синхронизации данных.

Enabled: true – сервис доступен и может быть запущен системой.

Exported: false – сервис не может быть запущен другими приложениями. Например, для сервиса синхронизации данных с медицинским устройством из основного приложения параметр указан в True для того, чтобы мы могли его перезапускать из приложения-компаньона.

Переопределяем метод запуска сервиса:

17.png

Для управления состоянием задачи используем переменную _workerTask

18.png

Методы репозиториев, выполняющих логику синхронизации, вызываем в методе SyncData

19.png

Получение зависимостей на репозитории выполняем по необходимости, обернув в конструктор Lazy

20.png

При завершении работы фонового сервиса отменяем выполняемую задачу

21.png

Для добавления в планировщик фоновой задачи обращаемся к сервису JobSchedulerService

22.png

Минимальная периодичность запуска сервиса – 15 минут, однако во время тестов на устройствах нескольких вендоров мы воспроизводили периодичность перезапуска не в фиксированное время, а в интервале – 15-30 минут.

Добавление фонового сервиса в планировщик выполняем после авторизации.

23.png

Для остановки текущего фонового процесса, например, при деавторизации (Logout) подписываемся на LogoutMessage в классе фонового сервиса и отменяем текущее выполнение задачи

24.png

А также удаляем из планировщика последующие плановые перезапуски задачи

25.png

Заключение

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

Описанные в статье приемы оптимизации фоновых процессов позволили достичь стабильной работы приложения даже в условиях повышенной нагрузки на систему Android. Настройка фоновых задач с использованием JobService обеспечила надежную синхронизацию данных и минимизировала нагрузку на устройство. Реализация сервисных push-уведомлений с помощью Firebase Cloud Messaging добавила дополнительный уровень надежности и автономности.

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

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

Связаться с нами
Форматы: jpg, png, xls, xlsx, doc, docx, pdf
Размер до 5 МБ
Нажимая кнопку «Отправить», вы принимаете правилами обработки персональных данных
Заявка отправлена

Спасибо за проявленный интерес к нашей компании, специалист свяжется с вами в ближайшее время