Как стать автором
Обновить

Принципы построения многомодульных Android-приложений

Время на прочтение 7 мин
Количество просмотров 14K
Автор оригинала: Android Developer

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

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

Принципы высокой сцепленности и слабой связанности

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

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

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

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

Типы модулей

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

Data-модули (модули данных).

Data-модуль обычно содержит репозиторий, источники данных и классы моделей. Три основные обязанности data-модуля:

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

  2. Предоставить репозиторий в качестве внешнего API. Общедоступный API data-модуля должен быть репозиторием, поскольку он отвечает за предоставление данных остальной части приложения.

  3. Скрыть доступ к деталям реализации и Data Source: Источники данных (Data Source) должны быть доступны только репозиториям из того же модуля. Они остаются скрытыми от доступа снаружи. Вы можете обеспечить это, используя ключевое слово private или internal visibility.

Рисунок 1. Пример Data-модулей и их структура. Оранжевым цветом выделены feature-модули, о которых будет ниже.
Рисунок 1. Пример Data-модулей и их структура. Оранжевым цветом выделены feature-модули, о которых будет ниже.

Feature-модули. Функциональные модули.

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

Каждая вкладка может быть отдельной фичей.
Каждая вкладка может быть отдельной фичей.

Фичи (feature) связаны с экранами или местами назначения в вашем приложении. Они, скорее всего, будут иметь связанный пользовательский интерфейс и ViewModel для обработки своей логики и состояния. Примером может быть функционал оплаты, авторизация пользователя. Функциональность не обязательно должна быть ограничена одним экраном или местом навигации. Feature-модули зависят от data-модулей.

Пример feature-модулей и их структура. Такие модули зависят от data-модулей.
Пример feature-модулей и их структура. Такие модули зависят от data-модулей.

App-модули. Модули приложения.

App-модули являются точкой входа в приложение. Они зависят от feature-модулей и обычно обеспечивают корневую навигацию. Один модуль приложения может быть скомпилирован в несколько разных двоичных файлов благодаря разным вариантам сборки (build variant).

Граф зависимостей для демо и полной версии приложения, в зависимости от выбранного варианта сборки
Граф зависимостей для демо и полной версии приложения, в зависимости от выбранного варианта сборки

Если ваше приложение предназначено для нескольких типов устройств, таких как android auto, android wear или android TV, вы можете разделить модули приложения для каждого из устройств. Это помогает отделить зависимости от конкретной платформы.

Граф зависимостей для приложения Android Auto
Граф зависимостей для приложения Android Auto

Common-модули. Общие модули.

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

  • UI-модуль. Если у вас есть настраиваемые UI-компоненты или сложные брендинг, вы можете выделить UI-виджеты в отдельный модуль для дальнейшего повторного использования. Это поможет сделать UI вашего приложения консистентным. Кроме того, если ваш UI выделен в отдельный модуль, то вы сможете избежать болезненного рефакторинга при ребрендинге.

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

  • Сетевой модуль: если многим модулям требуется сетевое подключение, вы можете рассмотреть возможность создания модуля, предназначенного для предоставления http-клиента. Это особенно полезно, когда вашему клиенту требуется индивидуальная конфигурация.

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

Взаимодействие между модулями.

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

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

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

navController.navigate("checkout/$bookId")

Используя в качестве аргумента id книги можно будет получить полную информацию о книге.

class CheckoutViewModel(savedStateHandle: SavedStateHandle, …) : ViewModel() {

   val uiState: StateFlow<CheckoutUiState> =
      savedStateHandle.getStateFlow<String>("bookId", "").map { bookId ->
          // produce UI state calling bookRepository.getBook(bookId)
      }
      …
}

Вы не должны передавать объекты в качестве аргументов для навигации. Вместо этого используйте простые идентификаторы, через которые можно загрузить данные используя data-слой. Таким образом, вы поддерживаете низкую связанность и не нарушаете принцип единственного источника истины (single source of truth principle).

В приведенном ниже примере оба feature-модуля зависят от одного и того же модуля данных (data:books). Это позволяет свести к минимуму объем данных, которые модуль-посредник должен пересылать, и поддерживает низкую связь между модулями. Вместо передачи объектов модули должны обмениваться идентификаторами примитивов и загружать ресурсы из общего data-модуля.

Два feature-модуля которые используют один общий data-модуль для получения данных.
Два feature-модуля которые используют один общий data-модуль для получения данных.

Общие рекомендации

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

Поддерживайте консистентную конфигурацию

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

Свести к минимуму публичный API

Публичный интерфейс модуля должен быть минимальным и отображать только самое необходимое. Детали реализации не должны быть доступны наружу. Используйте модификаторы private или internal  для уменьшения области видимости, чтобы сделать методы или свойства приватными для модуля. При объявлении зависимостей в вашем модуле отдавайте предпочтение подключению через implementation а не api. Последний предоставляет транзитивные зависимости потребителям вашего модуля. Использование implementation может сократить время сборки, поскольку уменьшает количество модулей, которые необходимо пересобрать после изменений.

Предпочитайте Kotlin и Java-модули вместо Android-модулей.

Android Studio поддерживает три основных типа модулей:

  • App-модули, модули приложения — это точка входа в ваше приложение. Они могут содержать исходный код, ресурсы, Activity и файл AndroidManifest.xml. Результатом модуля приложения является артефакт приложения Android (AAB или APK).

  • Модули библиотеки имеют то же содержимое, что и App-модули. Они используются другими модулями Android в качестве зависимости. Выходные данные библиотечного модуля — это Android-архив (AAR), структурно идентичные модулям приложения, но они скомпилированы в AAR, который впоследствии может использоваться другими модулями в качестве зависимости. Модуль библиотеки позволяет инкапсулировать и повторно использовать одну и ту же логику и ресурсы во многих App-модулях.

  • Kotlin и Java-модули не содержат никаких ресурсов Android, Activity или файлов Android-манифеста. Если в вашем модуле нет зависимости от Android SDK, желательно, чтобы вы использовали Kotlin или Java - модуль.

Не забудьте присоединиться к нам в Telegram, а на платформе  AndroidSchool.ru[ссылка удалена модератором] публикуются полезные материалы для Android-разработчика и современные туториалы.

Теги:
Хабы:
+2
Комментарии 11
Комментарии Комментарии 11

Публикации

Истории

Работа

Ближайшие события

PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн
Weekend Offer в AliExpress
Дата 20 – 21 апреля
Время 10:00 – 20:00
Место
Онлайн