Руководство по архитектуре приложения

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

Пользовательский опыт мобильного приложения

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

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

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

Общие архитектурные принципы

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

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

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

Разделение интересов

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

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

Управляйте пользовательским интерфейсом на основе моделей данных

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

Постоянные модели идеальны по следующим причинам:

  • Ваши пользователи не потеряют данные, если ОС Android уничтожит ваше приложение, чтобы освободить ресурсы.

  • Ваше приложение продолжает работать в случаях, когда сетевое соединение нестабильно или недоступно.

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

Единый источник истины

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

Этот шаблон дает несколько преимуществ:

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

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

Однонаправленный поток данных

Принцип единого источника истины часто используется в наших руководствах с шаблоном однонаправленного потока данных (UDF). В UDF состояние течет только в одном направлении. События , которые изменяют поток данных в противоположном направлении.

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

Этот шаблон лучше гарантирует согласованность данных, менее подвержен ошибкам, его легче отлаживать и он использует все преимущества шаблона SSOT.

В этом разделе показано, как структурировать ваше приложение в соответствии с рекомендуемыми рекомендациями.

Учитывая общие архитектурные принципы, упомянутые в предыдущем разделе, каждое приложение должно иметь как минимум два уровня:

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

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

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

Современная архитектура приложений

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

  • Реактивная и многоуровневая архитектура.
  • Однонаправленный поток данных (UDF) на всех уровнях приложения.
  • Уровень пользовательского интерфейса с держателями состояний для управления сложностью пользовательского интерфейса.
  • Сопрограммы и потоки.
  • Лучшие практики внедрения зависимостей.

Дополнительные сведения см. в следующих разделах, на других страницах «Архитектура» в оглавлении и на странице рекомендаций , содержащей сводку наиболее важных рекомендаций.

Уровень пользовательского интерфейса

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

Уровень пользовательского интерфейса состоит из двух вещей:

  • Элементы пользовательского интерфейса, отображающие данные на экране. Вы создаете эти элементы с помощью функций Views или Jetpack Compose .
  • Держатели состояний (например, классы ViewModel ), которые содержат данные, предоставляют их пользовательскому интерфейсу и обрабатывают логику.
В типичной архитектуре элементы пользовательского интерфейса уровня пользовательского интерфейса зависят от держателей состояний, которые, в свою очередь, зависят от классов либо уровня данных, либо дополнительного уровня домена.
Рисунок 2. Роль уровня пользовательского интерфейса в архитектуре приложения.

Чтобы узнать больше об этом слое, посетите страницу «Слой пользовательского интерфейса» .

Уровень данных

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

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

В типичной архитектуре репозитории уровня данных предоставляют данные остальной части приложения и зависят от источников данных.
Рисунок 3. Роль уровня данных в архитектуре приложения.

Классы репозитория отвечают за следующие задачи:

  • Предоставление данных остальной части приложения.
  • Централизация изменений в данных.
  • Разрешение конфликтов между несколькими источниками данных.
  • Абстрагирование источников данных от остальной части приложения.
  • Содержит бизнес-логику.

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

Дополнительную информацию об этом слое см. на странице уровня данных .

Слой домена

Уровень домена — это дополнительный уровень, который находится между уровнями пользовательского интерфейса и данных.

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

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

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

Чтобы узнать больше об этом слое, посетите страницу слоя домена .

Управление зависимостями между компонентами

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

  • Внедрение зависимостей (DI) . Внедрение зависимостей позволяет классам определять свои зависимости, не создавая их. Во время выполнения за предоставление этих зависимостей отвечает другой класс.
  • Локатор сервисов : шаблон локатора сервисов предоставляет реестр, в котором классы могут получать свои зависимости, а не создавать их.

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

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

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

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

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

Не храните данные в компонентах приложения.

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

Уменьшите зависимости от классов Android.

Компоненты вашего приложения должны быть единственными классами, которые полагаются на API-интерфейсы SDK платформы Android, такие как Context или Toast . Отделение от них других классов в вашем приложении помогает улучшить тестируемость и уменьшает связанность внутри вашего приложения.

Создайте четко определенные границы ответственности между различными модулями вашего приложения.

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

Как можно меньше подвергайте воздействию каждого модуля.

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

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

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

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

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

Типы отвечают за свою политику параллелизма.

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

Сохраняйте как можно больше актуальных и свежих данных.

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

Преимущества архитектуры

Наличие хорошей архитектуры в вашем приложении приносит много преимуществ проектным и инженерным группам:

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

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

Образцы

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

{% дословно %} {% дословно %} {% дословно %} {% endverbatim %} ,

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

Пользовательский опыт мобильного приложения

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

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

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

Общие архитектурные принципы

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

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

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

Разделение интересов

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

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

Управляйте пользовательским интерфейсом на основе моделей данных

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

Стойкие модели идеальны по следующим причинам:

  • Ваши пользователи не потеряют данные, если ОС Android уничтожит ваше приложение, чтобы освободить ресурсы.

  • Ваше приложение продолжает работать в случаях, когда сетевое соединение нестабильно или недоступно.

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

Единый источник истины

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

Этот шаблон дает несколько преимуществ:

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

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

Однонаправленный поток данных

Принцип единого источника истины часто используется в наших руководствах с шаблоном однонаправленного потока данных (UDF). В UDF состояние течет только в одном направлении. События , которые изменяют поток данных в противоположном направлении.

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

Этот шаблон лучше гарантирует согласованность данных, менее подвержен ошибкам, его легче отлаживать и он использует все преимущества шаблона SSOT.

В этом разделе показано, как структурировать ваше приложение в соответствии с рекомендуемыми рекомендациями.

Учитывая общие архитектурные принципы, упомянутые в предыдущем разделе, каждое приложение должно иметь как минимум два уровня:

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

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

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

Современная архитектура приложений

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

  • Реактивная и многоуровневая архитектура.
  • Однонаправленный поток данных (UDF) на всех уровнях приложения.
  • Уровень пользовательского интерфейса с держателями состояний для управления сложностью пользовательского интерфейса.
  • Сопрограммы и потоки.
  • Лучшие практики внедрения зависимостей.

Дополнительные сведения см. в следующих разделах, на других страницах «Архитектура» в оглавлении и на странице рекомендаций , содержащей сводку наиболее важных рекомендаций.

Уровень пользовательского интерфейса

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

Уровень пользовательского интерфейса состоит из двух вещей:

  • Элементы пользовательского интерфейса, отображающие данные на экране. Вы создаете эти элементы с помощью функций Views или Jetpack Compose .
  • Держатели состояний (такие как классы ViewModel ), которые содержат данные, предоставляют их пользовательскому интерфейсу и обрабатывают логику.
В типичной архитектуре элементы пользовательского интерфейса уровня пользовательского интерфейса зависят от держателей состояний, которые, в свою очередь, зависят от классов либо уровня данных, либо дополнительного уровня домена.
Рисунок 2. Роль уровня пользовательского интерфейса в архитектуре приложения.

Дополнительные сведения об этом слое см. на странице «Слой пользовательского интерфейса» .

Уровень данных

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

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

В типичной архитектуре репозитории уровня данных предоставляют данные остальной части приложения и зависят от источников данных.
Рисунок 3. Роль уровня данных в архитектуре приложения.

Классы репозитория отвечают за следующие задачи:

  • Предоставление данных остальной части приложения.
  • Централизация изменений в данных.
  • Разрешение конфликтов между несколькими источниками данных.
  • Абстрагирование источников данных от остальной части приложения.
  • Содержит бизнес-логику.

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

Дополнительную информацию об этом слое см. на странице уровня данных .

Слой домена

Уровень домена — это дополнительный уровень, который находится между уровнями пользовательского интерфейса и данных.

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

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

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

Чтобы узнать больше об этом слое, посетите страницу слоя домена .

Управление зависимостями между компонентами

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

  • Внедрение зависимостей (DI) . Внедрение зависимостей позволяет классам определять свои зависимости, не создавая их. Во время выполнения за предоставление этих зависимостей отвечает другой класс.
  • Локатор сервисов : шаблон локатора сервисов предоставляет реестр, в котором классы могут получать свои зависимости, а не создавать их.

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

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

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

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

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

Не храните данные в компонентах приложения.

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

Уменьшите зависимости от классов Android.

Компоненты вашего приложения должны быть единственными классами, которые полагаются на API-интерфейсы SDK платформы Android, такие как Context или Toast . Отделение от них других классов в вашем приложении помогает улучшить тестируемость и уменьшает связанность внутри вашего приложения.

Создайте четко определенные границы ответственности между различными модулями вашего приложения.

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

Как можно меньше подвергайте воздействию каждого модуля.

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

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

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

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

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

Типы отвечают за свою политику параллелизма.

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

Сохраняйте как можно больше актуальных и свежих данных.

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

Преимущества архитектуры

Наличие хорошей архитектуры в вашем приложении приносит много преимуществ проектным и инженерным группам:

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

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

Образцы

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

{ % Vorbatim %} { % endverbatim %} { % Vorbatim %} { % endverbatim %},

Это руководство охватывает лучшие практики и рекомендуется архитектуру для создания надежных высококачественных приложений.

Опыт пользователей мобильного приложения

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

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

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

Общие архитектурные принципы

Если вы не используете компоненты приложения для хранения данных и состояния приложений, как вы должны разрабатывать ваше приложение?

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

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

Разделение проблем

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

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

Привести пользовательский интерфейс из моделей данных

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

Постоянные модели идеально подходят для следующих причин:

  • Ваши пользователи не теряют данные, если ОС Android разрушает ваше приложение, чтобы освободить ресурсы.

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

Если вы основываете архитектуру приложения на классах модели данных, вы сделаете ваше приложение более тестируемым и надежным.

Единственный источник истины

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

Этот шаблон приносит несколько преимуществ:

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

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

Однонаправленный поток данных

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

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

Эта модель лучше гарантирует согласованность данных, менее склонна к ошибкам, легче отлаживать и приносит все преимущества схемы SSOT.

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

Учитывая общие архитектурные принципы, упомянутые в предыдущем разделе, каждое приложение должно иметь как минимум два уровня:

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

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

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

Современная архитектура приложения

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

  • Реактивная и многослойная архитектура.
  • Однонаправленный поток данных (UDF) во всех уровнях приложения.
  • Уровень пользовательского интерфейса с государственными держателями для управления сложностью пользовательского интерфейса.
  • Краутины и потоки.
  • Лучшие практики впрыскивания зависимости.

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

Ui слой

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

Слой пользовательского интерфейса состоит из двух вещей:

  • Элементы пользовательского интерфейса, которые отображают данные на экране. Вы создаете эти элементы, используя виды или функции JetPack .
  • Государственные держатели (такие как классы ViewModel ), которые содержат данные, подвергают их пользовательскому интерфейсу и обрабатывают логику.
В типичной архитектуре элементы пользовательского интерфейса пользовательского интерфейса зависят от владельцев состояний, что, в свою очередь, зависит от классов либо из уровня данных, либо от дополнительного домена.
Рисунок 2. Роль уровня пользовательского интерфейса в архитектуре приложения.

Чтобы узнать больше об этом слое, посмотрите страницу слоя пользовательского интерфейса .

Уровень данных

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

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

В типичной архитектуре репозитории уровня данных предоставляют данные для остальной части приложения и зависят от источников данных.
Рисунок 3. Роль уровня данных в архитектуре приложения.

Занятия репозитория отвечают за следующие задачи:

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

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

Чтобы узнать больше об этом уровне, см. Страницу уровня данных .

Домен слой

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

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

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

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

Чтобы узнать больше об этом слое, см. Страницу домена .

Управлять зависимостями между компонентами

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

  • Инъекция зависимости (DI) : инъекция зависимости позволяет классам определять свои зависимости, не построив их. Во время выполнения, другой класс отвечает за обеспечение этих зависимостей.
  • Локатор обслуживания : шаблон локатора обслуживания предоставляет реестр, в котором классы могут получить свои зависимости вместо их построения.

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

Мы рекомендуем следить за шаблонами впрыска зависимостей и использовать библиотеку рукояти в приложениях Android. Хилт автоматически строит объекты, ходя по дереву зависимости, обеспечивает гарантии времени компиляции на зависимости и создает контейнеры зависимости для классов Android Framework.

Общие лучшие практики

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

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

Не храните данные в компонентах приложения.

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

Уменьшить зависимости от классов Android.

Компоненты вашего приложения должны быть единственными классами, которые полагаются на API API Android Framework SDK, такие как Context или Toast . Абстракция других классов в вашем приложении от них помогает с тестируемостью и уменьшает связь в вашем приложении.

Создайте четко определенные границы ответственности между различными модулями в вашем приложении.

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

Разоблачить как можно меньше от каждого модуля.

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

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

Не заново переосмыслить колесо, снова и снова написав тот же код шаблона. Вместо этого сосредоточьтесь на своем времени и энергии на том, что делает ваше приложение уникальным, и пусть библиотеки JetPack и другие рекомендуемые библиотеки обрабатывают повторяющуюся пакет.

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

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

Типы отвечают за их политику параллелистики.

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

Сохраняется как можно более актуальные и свежие данные.

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

Преимущества архитектуры

Применение хорошей архитектуры в вашем приложении приносит много преимуществ для проектных и инженерных команд:

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

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

Образцы

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

{ % Vorbatim %} { % endverbatim %} { % Vorbatim %} { % endverbatim %}