- Что такое Android App Bundle и в чём его отличие от APK
- Как установить Android App Bundle
- Чем Android App Bundle лучше APK
- Архитектура Android-приложений… Правильный путь?
- Приступая к работе
- Наш сценарий
- Android-архитектура
- Presentation Layer (Слой представления)
- Domain Layer (Слой бизнес-логики)
- Data Layer (Слой данных)
- Обработка ошибок
- Тестирование
- Покажите мне код
- Заключение
- Как работает Android, часть 3
- Web vs desktop
- Activities & intents
- Tasks & back stack
- App lifecycle
- Services
- Broadcast receivers & content providers
Что такое Android App Bundle и в чём его отличие от APK
Наверное, все знают, что APK – это формат, в котором приложения для Android размещаются в Google Play и на сторонних платформах. Но если при загрузке из официального каталога установка происходит автоматически, и пользователь никак не взаимодействует с установочным файлом, то при использовании альтернативных площадок, всё происходит несколько иначе. Сначала вы скачиваете сам APK-файл и уже вручную его устанавливаете. Но некоторое время назад на альтернативных платформах, помимо привычных APK-файлов, стали появляться какие-то Android App Bundles. Разбираемся, что это и зачем вообще нужно.
Android App Bundle — это как APK, только лучше
Android App Bundle – это новый, так называемый «пакетный», формат приложений для Android. В отличие от APK, которые для успешной установки должны соответствовать параметрам смартфона, на который их устанавливают, AAB – это универсальный формат, который уже содержит в себе сведения обо всех устройствах и разных сочетаниях железа сразу.
Если вы откроете APKMirror – пожалуй, самый популярный альтернативный сайт с приложениями, — и перейдёте на страницу любого приложения, то увидите, что у него есть несколько разных версий APK. Каждая из них соответствует смартфонам с определёнными особенностями. Обычно это версия Android, тип процессора или показатель DPI, но бывают и другие.
Как установить Android App Bundle
Посмотрите, сколько APK-файлов у Instagram. Их все заменяет один пакет AAB
Android App Bundle представлены только в единственном экземпляре. Потому что они изначально созданы универсальными и совместимыми с различными устройствами, независимо от сочетаний их аппаратного обеспечения и технических характеристик. При установке пакет сам выдаст смартфону подходящий установочный файл, и тот его установит.
Что такое Camera2 API, зачем это нужно и как узнать, поддерживает ли её ваш смартфон
Поскольку Android App Bundle – это пакет различных компонентов, то они поставляются не в виде целостного файла, а в виде ZIP-архива. Это несёт как минимум одно существенное ограничение – AAB нельзя установить так же просто, как обычные APK-файлы просто по нажатию. С AAB это не прокатывает. Поэтому для их установки необходим специальный клиент, который всё распакует и установит вам на устройство.
Я для этой цели пользуюсь клиентом APKMirror. С ним установка Android App Bundle выглядит вот так:
- Скачайте установщик APKMirror по этой ссылке (один раз);
- Найдите и скачайте приложение в формате Android App Bundle;
Установить AAB как обычный APK-файл нельзя
- Нажмите на архив и откройте его с помощью APKMirror;
- Следуйте инструкциям, которые установщик выведет на экран.
Чем Android App Bundle лучше APK
Несмотря на то что что пакет AAB содержит базовый APK-файл, даже распаковав архив вручную, вы всё равно не сможете его установить. Дело в том, что пакет обычно включает в себя ещё ряд дополнительных компонентов, которые необходимы приложению для нормальной установки. Поэтому тут строго обязательно нужно-приложение установщик, которое работает со сторонними AAB. Так что Google Play для этой роли точно не годится.
В августе 2021 года Google полностью переходит на формат AAB
Может показаться, что всё это слишком сложно и запоминать всю последовательность действий, описанных выше, не имеет смысла. Однако это большое заблуждение, потому что уже в августе 2021 года Google откажется от использования классических APK. То есть все новые приложения и те, которые обновятся к тому времени, уже не будут иметь выделенных APK, а будут представлены на сторонних площадках только в виде AAB.
Google настаивает на использовании Android App Bundle, потому что они, несмотря на универсальность, более легковесны, чем классические APK, и их легче поддерживать. Формат AAB позволяет разработчикам создать только одну сборку приложения, которую будет проще обновлять, контролировать и совершенствовать. Так что учитесь работать с «бандлами», иначе останетесь без стороннего ПО.
Источник
Архитектура Android-приложений… Правильный путь?
От переводчика: Некоторые термины, которые использует автор, не имеют общепринятого перевода (ну, или я его не знаю:), поэтому я решил оставить большинство на языке оригинала — они всё равно понятны и для тех, кто пишет под android, но не знает английский.
Куда писать об ошибках и неточностях, вы знаете.
За последние несколько месяцев, а также после дискуссий на Tuenti с коллегами вроде @pedro_g_s и @flipper83 (кстати говоря, 2 крутых Android-разработчика), я решил, что имеет смысл написать заметку о проектировании Android-приложений.
Цель поста — немного рассказать о подходе к проектированию, который я продвигал в последние несколько месяцев, и также поделиться всем тем, что я узнал во время исследования и реализации этого подхода.
Приступая к работе
Мы знаем, что написание качественного программного обеспечения — сложное и многогранное занятие: программа должна не только соответствовать установленным требованиям, но и быть надёжной, удобной в сопровождении, тестируемой и достаточно гибкой для добавления или изменения функций. И здесь появляется понятие “стройной архитектуры”, которое неплохо бы держать в уме при разработке любого приложения.
Идея проста: стройная архитектура основывается на группе методов, реализующих системы, которые являются:
- Независимыми от фреймворков.
- Тестируемыми.
- Независимыми от UI.
- Независимыми от БД.
- Независимыми от любой внешней службы.
Кругов необязательно должно быть 4 (как на диаграмме), это просто схема; важно учитывать Правило Зависимостей: код должен иметь зависимости только во внутренние круги и не должен иметь никакого понятия, что происходит во внешних кругах.
Вот небольшой словарь терминов, необходимых для лучшего понимания данного подхода:
- Entities (Сущности): это бизнес-логика приложения.
- Use Cases (Методы использования): эти методы организуют поток данных в Entities и из них. Также их называют Interactors (Посредниками).
- Interface Adapters (Интерфейс-Адаптеры): этот набор адаптеров преобразовывает данные из формата, удобного для Методов использования и Сущностей. К этим адаптерами принадлежат Presenter-ы и Controller-ы.
- Frameworks and Drivers: место скопления деталей: UI, инструменты, фреймворки, БД и т. д.
Для лучшего понимания обратитесь к данной статье или к данному видео.
Наш сценарий
Я начал с простого, чтобы понять, как обстоят дела: создал простенькое приложение, которое отображает список друзей, который получает с облака, а при клике по другу отображает более детальную информацию о нём на новом экране.
Я оставлю здесь видео, чтобы вы поняли, о чем идёт речь:
Android-архитектура
Наша цель — разделение задач таким образом, чтобы бизнес-логика ничего не знала о внешнем мире, так, чтобы можно было тестировать её без никаких зависимостей и внешних элементов.
Для достижения этого я предлагаю разбить проект на 3 слоя, каждый из которых имеет свою цель и может работать независимо от остальных.
Стоит отметить, что каждый слой использует свою модель данных, таким образом можно достигнуть необходимой независимости (вы можете увидеть в коде, для выполнения трансформации данных необходим преобразователь данных (data mapper). Это вынужденная плата за то, чтобы модели внутри приложения не пересекались). Вот схема, как это выглядит:
Примечание: я не использовал внешних библиотек (кроме gson для парсинга json и junit, mockito, robolectric и espresso для тестирования) для того, чтобы пример был более наглядным. В любом случае, не стесняйтесь использовать ORM для хранения информации или любой dependency injection framework, да и вообще инструменты или библиотеки, которые делают вашу жизнь легче (помните: изобретать велосипед заново — не лучшая идея).
Presentation Layer (Слой представления)
Здесь логика связывается с Views (Представлениями) и происходят анимации. Это не что иное, как Model View Presenter (то есть, MVP), но вы можете использовать любой другой паттерн вроде MVC или MVVM. Я не буду вдаваться в детали, но fragments и activities — это всего лишь views, там нету никакой логики кроме логики UI и рендеринга этого самого отображения. Presenter-ы на этом слое связываются с interactors (посредниками), что предполагает работу в новом потоке (не в UI-потоке), и передачи через коллбэки информации, которая будет отображена во view.
Если вы хотите увидеть крутой пример эффективного Android UI, который использует MVP и MVVM, взгляните на пример реализации от моего друга Pedro Gómez.
Domain Layer (Слой бизнес-логики)
Вся логика реализована в этом слое. Рассматривая проект, вы увидите здесь реализацию interactor-ов (Use Cases — методы использования).
Этот слой — модуль на чистой джаве без никаких Android-зависимостей. Все внешние компоненты используют интерфейсы для связи с бизнес-объектами.
Data Layer (Слой данных)
Все данные, необходимые для приложения, поставляются из этого слоя через реализацию UserRepository (интерфейс находится в domain layer — слое бизнес-логики), который использует Repository Pattern со стратегией, которая, через фабрику, выбирает различные источники данных, в зависимости от определенных условий.
Например, для получения конкретного пользователя по id источником данных выбирается дисковый кэш, если пользователь уже загружен в него, в противном случае облаку отправляется запрос на получение данных для дальнейшего сохранения в тот же кэш.
Идея всего этого заключается в том, что происхождение данных является понятным для клиента, которого не волнует, поступают данные из памяти, кэша или облака, ему важно только то, что данные будут получены и доступны.
Примечание: что касается кода, помня, что код — это учебный пример, я реализовал очень простой, даже примитивный кэш, используя хранения shared preferences. Помните: НЕ СТОИТ ИЗОБРЕТАТЬ ВЕЛОСИПЕД, если существуют библиотеки, хорошо решающие поставленную задачу.
Обработка ошибок
Это большая тема, в которой всегда есть, что обсудить (и здесь автор предлагает делиться своими решениями). Что до моей реализации, я использовал коллбэки, которые, на случай, если что-то случается, скажем, в хранилище данных, имеют 2 метода: onResponse() и onError(). Последний инкапсулирует исключения во wrapper class (класс-обертку) под названием “ErrorBundle”: Такой подход сопряжен с некоторыми трудностями, потому что бывают цепочки обратных вызовов один за другим, пока ошибка не выходит на presentation layer, чтобы отобразиться. Читаемость кода из-за этого может быть немного нарушена.
С другой стороны, я реализовал систему event bus, которая бросает события, если что-то не так, но такое решение похоже на использование GOTO, и, по-моему, иногда, если не управлять событиями предельно внимательно, можно заблудиться в цепи событий, особенно когда их одновременно бросается несколько.
Тестирование
Что касается тестирования, я применил несколько решений, в зависимости от слоя.
- Presentation Layer: существующий android инструментал и espresso для интеграции и функционального тестирования.
- Domain Layer: JUnit + mockito использовались для юнит-тестов.
- Data Layer: Robolectric (так как этот слой имеет android-зависимости) + junit + для интеграции и юнит-тестов.
Покажите мне код
Думаю, в этом месте вам интересно посмотреть на код. Что ж, вот ссылка на github, где можно найти, что же у меня получилось. Что стоит упомянуть о структуре папок так это то, что разные слои представлены разными модулями:
- presentation: это android-модуль для presentation layer.
- domain: java-модуль без android-зависимостей.
- data: android-модуль, откуда поступают все данные.
- data-test: тесты для data layer. Из-за определённых ограничений, при использовании Robolectric мне пришлось использовать отдельный java-модуль.
Заключение
Дядя Боб сказал: “Архитектура — это намерения, а не фреймворки”, и я полностью с ним согласен. Конечно, есть разные способы реализовать какую-либо вещь, и я уверен, что вы, как и я, каждый день сталкиваетесь с трудностями в этой области, но используя данные приёмы, вы сможете быть уверены, что ваше приложение будет:
- Простым в поддержке.
- Простым в тестировании.
- Составлять единое целое,
- Будучи разделённым.
В заключение я настоятельно рекомендую вам попробовать эти методы, посмотреть на результат и поделиться своим опытом как в отношении этого, так любого другого подхода, который, по вашим наблюдениям, работает лучше: мы уверены, что постоянное совершенствование всегда полезно и положительно.
Источник
Как работает Android, часть 3
В этой статье я расскажу о компонентах, из которых состоят приложения под Android, и об идеях, которые стоят за этой архитектурой.
Web vs desktop
Если задуматься об отличиях современных веб-приложений от «обычных» десктопных приложений, можно — среди недостатков — выделить несколько преимуществ веба:
- Веб-приложения переносимы между архитектурами и платформами, как и Java.
- Веб-приложения не требуют установки и всегда обновлены, как и Android Instant Apps.
Кроме того, веб-приложения существуют в виде страниц, которые могут ссылаться друг на друга — как в рамках одного сайта, так и между сайтами. При этом страница на одном сайте не обязана ограничиваться ссылкой только на главную страницу другого, она может ссылаться на конкретную страницу внутри другого сайта (это называется deep linking). Ссылаясь друг на друга, отдельные сайты объединяются в общую сеть, веб.
Несколько копий одной страницы — например, несколько профилей в социальной сети — могут быть одновременно открыты в нескольких вкладках браузера. Интерфейс браузера рассчитан на переключение между одновременными сессиями (вкладками), а не между отдельными сайтами — в рамках одной вкладки вы можете перемещаться по ссылкам (и вперёд-назад по истории) между разными страницами разных сайтов.
Всё это противопоставляется «десктопу», где каждое приложение работает отдельно и часто независимо от других — и в этом плане то, как устроены приложения в Android, гораздо ближе к вебу, чем к «традиционным» приложениям.
Activities & intents
Основной вид компонентов приложений под Android — это activity. Activity — это один «экран» приложения. Activity можно сравнить со страницей в вебе и с окном приложения в традиционном оконном интерфейсе.
Собственно окна в Android тоже есть на более низком уровне — уровне window manager. Каждой activity обычно соответствует своё окно. Чаще всего окна activity развёрнуты на весь доступный экран, но:
- Во-первых, Android поддерживает мультиоконный режим — split-screen, picture-in-picture и даже freeform.
- Во-вторых, Android поддерживает подключение нескольких дисплеев.
- В-третьих, activity может намеренно занимать небольшую часть экрана ( Theme_Dialog ).
Например, в приложении для электронной почты (email client) могут быть такие activity, как Inbox Activity (список входящих писем), Email Activity (чтение одного письма), Compose Activity (написание письма) и Settings Activity (настройки).
Как и страницы одного сайта, activity одного приложения могут запускаться как друг из друга, так и независимо друг от друга (другими приложениями). Если в вебе на другую страницу обращаются по URL (ссылке), то в Android activity запускаются через intent’ы.
Intent — это сообщение, которое указывает системе, что нужно «сделать» (например, открыть данный URL, написать письмо на данный адрес, позвонить на данный номер телефона или сделать фотографию).
Приложение может создать такой intent и передать его системе, а система решает, какая activity (или другой компонент) будет его выполнять (handle). Эта activity запускается системой (в существующем процессе приложения или в новом, если он ещё не запущен), ей передаётся этот intent, и она его выполняет.
Стандартный способ создавать intent’ы — через соответствующий класс в Android Framework. Для работы с activity и intent’ами из командной строки в Android есть команда am — обёртка над стандартным классом Activity Manager:
Intent’ы могут быть явными (explicit) и неявными (implicit). Явный intent указывает идентификатор конкретного компонента, который нужно запустить — чаще всего это используется, чтобы запустить из одной activity другую внутри одного приложения (при этом intent может даже не содержать другой полезной информации).
Неявный intent обязательно должен указывать действие, которое нужно сделать. Каждая activity (и другие компоненты) указывают в манифесте приложения, какие intent’ы они готовы обрабатывать (например, ACTION_VIEW для ссылок с доменом https://example.com ). Система выбирает подходящий компонент среди установленных и запускает его.
Если в системе есть несколько activity, которые готовы обработать intent, пользователю будет предоставлен выбор. Обычно это случается, когда установлено несколько аналогичных приложений, например несколько браузеров или фоторедакторов. Кроме того, приложение может явно попросить систему показать диалог выбора (на самом деле при этом переданный intent оборачивается в новый intent с ACTION_CHOOSER ) — это обычно используется для создания красивого диалога Share:
Кроме того, activity может вернуть результат в вызвавшую её activity. Например, activity в приложении-камере, которая умеет обрабатывать intent «сделать фотографию» ( ACTION_IMAGE_CAPTURE ) возвращает сделанную фотографию в ту activity, которая создала этот intent.
При этом приложению, содержащему исходную activity, не нужно разрешение на доступ к камере.
Таким образом, правильный способ приложению под Android сделать фотографию — это не потребовать разрешения на доступ к камере и использовать Camera API, а создать нужный intent и позволить системному приложению-камере сделать фото. Аналогично, вместо использования разрешения READ_EXTERNAL_STORAGE и прямого доступа к файлам пользователя стоит дать пользователю возможность выбрать файл в системном файловом менеджере (тогда исходному приложению будет разрешён доступ именно к этому файлу).
A unique aspect of the Android system design is that any app can start another app’s component. For example, if you want the user to capture a photo with the device camera, there’s probably another app that does that and your app can use it instead of developing an activity to capture a photo yourself. You don’t need to incorporate or even link to the code from the camera app. Instead, you can simply start the activity in the camera app that captures a photo. When complete, the photo is even returned to your app so you can use it. To the user, it seems as if the camera is actually a part of your app.
При этом «системное» приложение — не обязательно то, которое было предустановлено производителем (или автором сборки Android). Все установленные приложения, которые умеют обрабатывать данный intent, в этом смысле равны между собой. Пользователь может выбрать любое из них в качестве приложения по умолчанию для таких intent’ов, а может выбирать нужное каждый раз. Выбранное приложение становится «системным» в том смысле, что пользователь выбрал, чтобы именно оно выполняло все задачи (то есть intent’ы) такого типа, возникающие в системе.
Само разрешение на доступ к камере нужно только тем приложениям, которые реализуют свой интерфейс камеры — например, собственно приложения-камеры, приложения для видеозвонков или дополненной реальности. Наоборот, обыкновенному мессенджеру доступ к камере «чтобы можно было фото отправлять» не нужен, как не нужен и доступ к совершению звонков приложению крупного банка.
Этой логике подчиняются даже такие «части системы», как, например, домашний экран (лончер, launcher). Лончер — это специальное приложение со своими activity (которые используют специальные флаги вроде excludeFromRecents и launchMode=»singleTask» ).
Нажатие кнопки «домой» создаёт intent категории HOME , который дальше проходит через обычный механизм обработки intent’ов — в том числе, если в системе установлено несколько лончеров и ни один не выбран в качестве лончера по умолчанию, система отобразит диалог выбора.
«Запуск» приложения из лончера тоже происходит через intent. Лончер создаёт явный intent категории LAUNCHER , который «обрабатывается» запуском основной activity приложения.
Приложение может иметь несколько activity, которые поддерживают такой intent, и отображаться в лончере несколько раз (при этом может понадобиться указать им разную taskAffinity ). Или не иметь ни одной и не отображаться в лончере вообще (но по-прежнему отображаться в полном списке установленных приложений в настройках). «Обычные» приложения так делают довольно редко; самый известный пример такого поведения — Google Play Services.
Многие операционные системы делятся на собственно операционную систему и приложения, установленные поверх, ничего друг о друге не знающие и не умеющие взаимодействовать. Система компонентов и intent’ов Android позволяет приложениям, по-прежнему абсолютно ничего друг о друге не зная, составлять для пользователя один интегрированный системный user experience — установленные приложения реализуют части одной большой системы, они составляют из себя систему. И это, с одной стороны, происходит прозрачно для пользователя, с другой — представляет неограниченные возможности для кастомизации.
По-моему, это очень красиво.
Tasks & back stack
Как я уже говорил, в браузере пользователь может переключаться не между сайтами, а между вкладками, история каждой из которых может содержать много страниц разных сайтов. Аналогично, в Android пользователь может переключаться между задачами (tasks), которые отображаются в виде карточек на recents screen. Каждая задача представляет собой back stack — несколько activity, «наложенных» друг на друга.
Когда одна activity запускает другую, новая activity помещается в стек поверх старой. Когда верхняя activity в стеке завершается — например, когда пользователь нажимает системную кнопку «назад» — предыдущая activity в стеке снова отображается на экране.
Каждый стек может включать в себя activity из разных приложений, и несколько копий одной activity могут быть одновременно открыты в рамках разных задач или даже внутри одного стека.
При запуске новой activity могут быть указаны специальные флаги, такие как singleTop , singleTask , singleInstance и CLEAR_TOP , которые модифицируют этот механизм. Например, приложения-браузеры обычно разрешают запуск только одной копии своей основной activity, и для переключения между открытыми страницами реализуют собственную систему вкладок. С другой стороны, Custom Tabs — пример activity в браузере (чаще всего Chrome), которая ведёт себя почти «как обычно», то есть показывает только одну страницу, но позволяет одновременно открывать несколько своих копий.
App lifecycle
Одно из основных ограничений встраиваемых и мобильных устройств — небольшое количество оперативной памяти (RAM). Если современные флагманские устройства уже оснащаются несколькими гигабайтами оперативной памяти, то в первом смартфоне на Android, HTC Dream (он же T-Mobile G1), вышедшем в сентябре 2008 года, её было всего 192 мегабайта.
Проблема ограниченной памяти дополнительно осложняется тем, что в мобильных устройствах, в отличие от «обычных» компьютеров, не используются swap-разделы (и swap-файлы) — в том числе и из-за низкой (по сравнению с SSD и HDD) скорости доступа к SD-картам и встроенной флеш-памяти, где они могли бы размещаться. Начиная с версии 4.4 KitKat, Android использует zRAM swap, то есть эффективно сжимает малоиспользуемые участки памяти. Тем не менее, проблема ограниченной памяти остаётся.
Если все процессы представляют собой для системы чёрный ящик, лучшая из возможных стратегия поведения в случае нехватки свободной памяти — принудительно завершать («убивать») какие-то процессы, что и делает Linux Out Of Memory (OOM) Killer. Но Android знает, что происходит в системе, ему известно, какие приложения и какие их компоненты запущены, что позволяет реализовать гораздо более «умную» схему освобождения памяти.
Во-первых, когда свободная память заканчивается, Android явно просит приложения освободить ненужную память (например, сбросить кэш), вызывая методы onTrimMemory / onLowMemory . Во-вторых, Android может эффективно проводить сборку мусора в фоновых приложениях, освобождая память, которую они больше не используют (на уровне Java), при этом не замедляя работу текущего приложения.
Но основной механизм освобождения памяти в Android — это завершение наименее используемых приложений. Система автоматически выбирает приложения, наименее важные для пользователя (например, те, из которых пользователь давно ушёл), даёт их компонентам шанс дополнительно освободить ресурсы, вызывая такие методы, как onDestroy , и завершает их, полностью освобождая используемую ими память и ресурсы.
Если пользователь возвращается в activity приложения, завершённого системой из-за нехватки памяти, эта activity запускается снова. При этом перезапуск происходит прозрачно для пользователя, поскольку activity сохраняет своё состояние при завершении ( onSaveInstanceState ) и восстанавливает его при последующем запуске. Реализованные в Android Framework виджеты используют этот механизм, чтобы автоматически сохранить состояние интерфейса (UI) при перезапуске — с точностью до введённого в EditText текста, положения курсора, позиции прокрутки (scroll) и т.д. Разработчик приложения может дополнительно реализовать сохранение и восстановление каких-то ещё данных, специфичных для этого приложения.
Подчеркну, что Android может перезапускать приложения не полностью, а покомпонентно, оставляя неиспользуемые части завершёнными — например, из двух копий одной activity одна может быть перезапущена, а другая остаться завершённой.
С точки зрения пользователя этот механизм похож на использование swap: в обоих случаях при возвращении в выгруженную часть приложения приходится немного подождать, пока она загружается снова — в одном случае, с диска, в другом — пересоздаётся по сохранённому состоянию.
Именно этот механизм автоматического перезапуска и восстановления состояния создаёт у пользователя ощущение, что приложения «запущены всегда», избавляя его от необходимости явно запускать и закрывать приложения и сохранять введённые в них данные.
Services
Приложениям может потребоваться выполнять действия, не связанные напрямую ни с какой activity, в том числе, продолжать делать их в фоне, когда все activity этого приложения завершены. Например, приложение может скачивать из сети большой файл, обрабатывать фотографии, воспроизводить музыку, синхронизировать данные или просто поддерживать TCP-соединение с сервером для получения уведомлений.
Такую функциональность нельзя реализовывать, просто запуская отдельный поток — это было бы для системы чёрным ящиком; в том числе, процесс был бы завершён при завершении всех activity, независимо от состояния таких фоновых операций. Вместо этого Android предлагает использовать ещё один вид компонентов — сервис.
Сервис нужен, чтобы сообщить системе, что в процессе приложения выполняются действия, которые не являются частью activity этого приложения. Сам по себе сервис не означает создание отдельного потока или процесса — его точки входа (entry points) запускаются в основном потоке приложения. Обычно реализация сервиса запускает дополнительные потоки и управляет ими самостоятельно.
Сервисы во многом похожи на activity: они тоже запускаются с помощью intent’ов и могут быть завершены системой при нехватке памяти.
Запущенные сервисы могут быть в трёх состояниях:
- Foreground service — сервис, выполняющий действие, состояние которого важно для пользователя, например, загрузка файла или воспроизведение музыки. Такой сервис обязан отображать уведомление в системной шторке уведомлений (примеры: состояние загрузки, название текущей песни и управление воспроизведением). Система считает такой сервис примерно настолько же важным для пользователя, как и текущая activity, и завершит его только в крайнем случае.
Background service — сервис, выполняющий фоновое действие, состояние которого не интересует пользователя (чаще всего, синхронизацию). Такие сервисы могут быть завершены при нехватке памяти с гораздо большей вероятностью. В старых версиях Android большое количество одновременно запущенных фоновых сервисов часто становилось причиной «тормозов»; начиная с версии 8.0 Oreo, Android серьёзно ограничивает использование фоновых сервисов, принудительно завершая их через несколько минут после того, как пользователь выходит из приложения.
Bound service — сервис, обрабатывающий входящее Binder-подключение. Такие сервисы предоставляют какую-то функциональность для других приложений или системы (например, WallpaperService и Google Play Services). В этом случае система может автоматически запускать сервис при подключении к нему клиентов и останавливать его при их отключении.
Рекомендуемый способ выполнять фоновые действия — использование JobScheduler, системного механизма планирования фоновой работы. JobScheduler позволяет приложению указать критерии запуска сервиса, такие как:
- Доступность сети. Здесь приложение может указать, требуется ли этому сервису наличие сетевого подключения, и если да, то возможна ли работа в роуминге или при использовании лимитного (metered) подключения.
- Подключение к источнику питания, что позволяет сервисам выполняться, не «сажая батарею».
- Бездействие (idle), что позволяет сервисам выполняться, пока устройство не используется, не замедляя работу во время активного использования.
- Обновления контента — например, появление новой фотографии.
- Период и крайний срок запуска — например, очистка кэша может производиться ежедневно, а синхронизация событий в календаре — каждый час.
JobScheduler планирует выполнение (реализованное как вызов через Binder) зарегистрированных в нём сервисов в соответствии с указанными критериями. Поскольку JobScheduler — общесистемный механизм, он учитывает при планировке критерии зарегистрированных сервисов всех установленных приложений. Например, он может запускать сервисы по очереди, а не одновременно, чтобы предотвратить резкую нагрузку на устройство во время использования, и планировать периодическое выполнение нескольких сервисов небольшими группами (batch), чтобы предотвратить постоянное энергозатратное включение-выключение радиооборудования.
Как можно заметить, использование JobScheduler не может заменить собой одного из вариантов использования фоновых сервисов — поддержания TCP-соединения с сервером для получения push-уведомлений. Если бы Android предоставлял приложениям такую возможность, устройству пришлось бы держать все приложения, соединяющиеся со своими серверами, запущенными всё время, а это, конечно, невозможно.
Решение этой проблемы — специальные push-сервисы, самый известный из которых — Firebase Cloud Messaging от Google (бывший Google Cloud Messaging).
Клиентская часть FCM реализована в приложении Google Play Services. Это приложение, которое специальным образом исключается из обычных ограничений на фоновые сервисы, поддерживает одно соединение с серверами Google. Разработчик, желающий отправить своему приложению push-уведомление, пересылает его через серверную часть FCM, после чего приложение Play Services, получив сообщение, передаёт его приложению, которому оно предназначено.
Такая схема позволяет, с одной стороны, мгновенно доставлять push-уведомления всем приложениям (не дожидаясь следующего периода синхронизации), с другой стороны, не держать множество приложений одновременно запущенными.
Broadcast receivers & content providers
Кроме activity и сервисов, у приложений под Android есть два других вида компонентов, менее интересных для обсуждения — это broadcast receiver’ы и content provider’ы.
Broadcast receiver — компонент, позволяющий приложению принимать broadcast’ы, специальный вид сообщений от системы или других приложений. Исходно broadcast’ы, как следует из названия, в основном использовались для рассылки широковещательных сообщений всем подписавшимся приложениям — например, система посылает сообщение AIRPLANE_MODE_CHANGED при включении или отключении самолётного режима.
Сейчас вместо подписки на такие broadcast’ы, как NEW_PICTURE и NEW_VIDEO , приложения должны использовать JobScheduler. Broadcast’ы используются либо для более редких событий (таких как BOOT_COMPLETED ), либо с явными intent’ами, то есть именно в качестве сообщения от одного приложения к другому.
Content provider — компонент, позволяющий приложению предоставлять другим приложениям доступ к данным, которыми оно управляет. Пример данных, доступ к которым можно получить таким образом — список контактов пользователя.
При этом приложение может хранить сами данные каким угодно образом, в том числе на устройстве в виде файлов, в настоящей базе данных (SQLite) или запрашивать их с сервера по сети. В этом смысле content provider — это унифицированный интерфейс для доступа к данным, независимо от формы их хранения.
Взаимодействие с content provider’ом во многом похоже на доступ к удалённой базе данных через REST API. Приложение-клиент запрашивает данные по URI (например, content://com.example.Dictionary.provider/words/42 ) через ContentResolver. Приложение-сервер определяет, к какому именно набору данных был сделан запрос, используя UriMatcher , и выполняет запрошенное действие (query, insert, update, delete).
Именно поверх content provider’ов реализован Storage Access Framework, позволяющий приложениям, хранящим файлы в облаке (например, Dropbox и Google Photos) предоставлять доступ к ним остальным приложениям, не занимая место на устройстве полной копией всех хранящихся в облаке файлов.
В следующей статье я расскажу о процессе загрузки Android, о содержимом файловой системы, о том, как хранятся данные пользователя и приложений, и о root-доступе.
Источник