В этом руководстве представлены лучшие практики и рекомендуемая архитектура для создания надежных высококачественных приложений.
Пользовательский опыт мобильного приложения
Типичное приложение Android содержит несколько компонентов , включая действия , фрагменты , сервисы , поставщики контента и приёмники вещания . Большинство этих компонентов приложения объявляются в манифесте приложения . Затем ОС Android использует этот файл, чтобы решить, как интегрировать ваше приложение в общий пользовательский интерфейс устройства. Учитывая, что типичное приложение Android может содержать несколько компонентов, а пользователи часто взаимодействуют с несколькими приложениями за короткий промежуток времени, приложения должны адаптироваться к различным типам рабочих процессов и задач, управляемых пользователем.
Несколько форм-факторов
Приложения могут работать на устройствах различных форм-факторов, включая не только телефоны, но и планшеты, складные устройства, устройства ChromeOS и другие. Приложение не может принимать портретную или альбомную ориентацию; оно может работать в любом из них или в обоих одновременно в течение одного сеанса. Изменения конфигурации, например, когда пользователь меняет положение складного устройства на положение стола или книги , могут привести к перекомпоновке пользовательского интерфейса приложения, что может повлиять на данные и состояние приложения.
Ограничения ресурсов
Помните, что мобильные устройства также ограничены в ресурсах, поэтому в любой момент операционная система может остановить некоторые процессы приложений, чтобы освободить место для новых.
Переменные условия запуска
Учитывая условия этой среды, компоненты вашего приложения могут запускаться по отдельности и не по порядку, и операционная система или пользователь могут их уничтожить в любой момент. Поскольку эти события находятся вне вашего контроля, вам не следует хранить в памяти какие-либо данные или состояние приложения в компонентах приложения, и компоненты приложения не должны зависеть друг от друга.
Общие архитектурные принципы
Если вам не следует использовать компоненты приложения для хранения данных и состояния приложения, как вам следует проектировать свое приложение?
По мере роста размера приложений Android важно определить архитектуру, которая позволит приложению масштабироваться, адаптироваться к разным формам и размерам, повысит надежность приложения и упростит его тестирование.
Архитектура приложения определяет границы между его частями и обязанности каждой части. Чтобы соответствовать всем требованиям, при проектировании архитектуры приложения необходимо следовать нескольким конкретным принципам.
Разделение интересов
Самый важный принцип — разделение ответственности . Распространенная ошибка — писать весь код в Activity
или Fragment
. Эти классы, ориентированные на пользовательский интерфейс, должны содержать только логику, отвечающую за взаимодействие с пользовательским интерфейсом и операционной системой. Сохраняя эти классы максимально компактными, можно избежать многих проблем, связанных с жизненным циклом компонентов, и улучшить их тестируемость.
Имейте в виду, что вы не владеете реализациями Activity
и Fragment
; это всего лишь связующие классы, представляющие собой контракт между ОС Android и вашим приложением. Операционная система может удалить их в любой момент в зависимости от действий пользователя или из-за системных проблем, таких как нехватка памяти. Чтобы обеспечить удовлетворительный пользовательский опыт и более управляемое обслуживание приложения, лучше всего минимизировать зависимость от них.
Адаптивные макеты
Ваше приложение должно корректно обрабатывать изменения конфигурации, например , переключение между портретной и альбомной ориентацией устройства, а также изменение размера экрана, например, при работе приложения на больших экранах . Приложение, реализующее канонические адаптивные макеты, обеспечивает оптимальный пользовательский опыт на устройствах с различными форм-факторами.
Пользовательский интерфейс на основе моделей данных
Другой важный принцип заключается в том, что пользовательский интерфейс должен разрабатываться на основе моделей данных, предпочтительно постоянных. Модели данных представляют данные приложения. Они независимы от элементов пользовательского интерфейса и других компонентов приложения. Это означает, что они не привязаны к жизненному циклу пользовательского интерфейса и компонентов приложения, но всё равно будут уничтожены, когда ОС решит удалить процесс приложения из памяти.
Постоянные модели идеальны по следующим причинам:
Ваши пользователи не потеряют данные, если ОС Android уничтожит ваше приложение, чтобы освободить ресурсы.
Ваше приложение продолжает работать даже в случаях, когда сетевое соединение нестабильно или отсутствует.
Если вы основываете архитектуру своего приложения на классах модели данных, вы делаете его более тестируемым и надежным.
Единый источник истины
При определении нового типа данных в вашем приложении необходимо назначить ему единый источник истины (SSOT). SSOT является владельцем этих данных, и только он может изменять или мутировать их. Для этого SSOT предоставляет данные, используя неизменяемый тип, а для изменения данных SSOT предоставляет функции или принимает события, которые могут вызывать другие типы.
Этот шаблон имеет множество преимуществ:
- Он централизует все изменения определенного типа данных в одном месте.
- Он защищает данные таким образом, что другие лица не могут их подделать.
- Это облегчает отслеживание изменений в данных. Таким образом, ошибки легче обнаружить.
В офлайн-приложениях источником данных обычно служит база данных. В некоторых случаях источником данных может быть ViewModel или даже пользовательский интерфейс.
Однонаправленный поток данных
Принцип единственного источника истины часто используется в наших руководствах в рамках шаблона «Однонаправленный поток данных» (UDF). В UDF состояние передается только в одном направлении. События , изменяющие поток данных, действуют в противоположном направлении.
В Android состояние или данные обычно передаются из типов с более высокой областью действия в иерархии к типам с более низкой областью действия. События обычно инициируются типами с более низкой областью действия, пока не достигнут SSOT для соответствующего типа данных. Например, данные приложения обычно передаются из источников данных в пользовательский интерфейс. Пользовательские события, такие как нажатия кнопок, передаются из пользовательского интерфейса в SSOT, где данные приложения изменяются и предоставляются в неизменяемом типе.
Этот шаблон лучше поддерживает согласованность данных, менее подвержен ошибкам, его легче отлаживать и он использует все преимущества шаблона SSOT.
Рекомендуемая архитектура приложения
В этом разделе показано, как структурировать приложение, следуя рекомендуемым практикам.
Учитывая общие архитектурные принципы, упомянутые в предыдущем разделе, каждое приложение должно иметь как минимум два слоя:
- Слой пользовательского интерфейса , отображающий данные приложения на экране.
- Уровень данных , содержащий бизнес-логику вашего приложения и предоставляющий доступ к данным приложения.
Вы можете добавить дополнительный слой, называемый слоем домена , чтобы упростить и повторно использовать взаимодействия между слоями пользовательского интерфейса и данных.

Современная архитектура приложений
Современная архитектура приложений поощряет использование, среди прочего, следующих методов:
- Адаптивная и многоуровневая архитектура.
- Однонаправленный поток данных (UDF) на всех уровнях приложения.
- Уровень пользовательского интерфейса с держателями состояний для управления сложностью пользовательского интерфейса.
- Сопрограммы и потоки.
- Лучшие практики внедрения зависимостей.
Более подробную информацию см. в следующих разделах, на других страницах «Архитектура» в содержании и на странице рекомендаций , содержащей сводку наиболее важных передовых практик.
Уровень пользовательского интерфейса
Роль слоя пользовательского интерфейса (или слоя представления ) заключается в отображении данных приложения на экране. При каждом изменении данных, будь то в результате взаимодействия пользователя (например, нажатия кнопки) или внешнего ввода (например, ответа сети), пользовательский интерфейс должен обновляться, отражая эти изменения.
Уровень пользовательского интерфейса состоит из двух частей:
- Элементы пользовательского интерфейса, отображающие данные на экране. Эти элементы создаются с помощью Views или функций Jetpack Compose . Как Views, так и Jetpack Compose поддерживают адаптивную компоновку.
- Держатели состояний (например, классы ViewModel ), которые хранят данные, предоставляют их пользовательскому интерфейсу и обрабатывают логику.

Более подробную информацию об этом слое см. на странице слоя пользовательского интерфейса .
Уровень данных
Уровень данных приложения содержит бизнес-логику . Именно бизнес-логика определяет ценность вашего приложения: она состоит из правил, определяющих, как ваше приложение создаёт, хранит и изменяет данные.
Уровень данных состоит из репозиториев , каждый из которых может содержать от нуля до нескольких источников данных . Вам следует создать класс репозитория для каждого типа данных, обрабатываемых в вашем приложении. Например, можно создать класс MoviesRepository
для данных, связанных с фильмами, или класс PaymentsRepository
для данных, связанных с платежами.

Классы репозитория отвечают за следующие задачи:
- Предоставление данных остальной части приложения.
- Централизация изменений данных.
- Разрешение конфликтов между несколькими источниками данных.
- Абстрагирование источников данных от остальной части приложения.
- Содержит бизнес-логику.
Каждый класс источника данных должен отвечать за работу только с одним источником данных, которым может быть файл, сетевой источник или локальная база данных. Классы источников данных служат связующим звеном между приложением и системой для операций с данными.
Более подробную информацию об этом слое можно найти на странице слоя данных .
Доменный слой
Уровень домена — это необязательный уровень, который располагается между уровнями пользовательского интерфейса и уровнями данных.
Уровень предметной области отвечает за инкапсуляцию сложной бизнес-логики или более простой бизнес-логики, которая повторно используется несколькими ViewModel. Этот уровень необязателен, поскольку не все приложения предъявляют подобные требования. Используйте его только при необходимости, например, для решения сложных задач или обеспечения возможности повторного использования.

Классы на этом уровне обычно называются вариантами использования или интеракторами . Каждый вариант использования должен отвечать за одну функциональность. Например, ваше приложение может иметь класс GetTimeZoneUseCase
, если несколько ViewModel используют часовые пояса для отображения правильного сообщения на экране.
Более подробную информацию об этом слое можно найти на странице, посвященной доменному слою .
Управление зависимостями между компонентами
Классы в вашем приложении зависят от других классов для корректной работы. Вы можете использовать любой из следующих шаблонов проектирования для сбора зависимостей конкретного класса:
- Внедрение зависимостей (DI) : Внедрение зависимостей позволяет классам определять свои зависимости без их создания. Во время выполнения за предоставление этих зависимостей отвечает другой класс.
- Локатор служб : Шаблон локатора служб предоставляет реестр, в котором классы могут получать свои зависимости вместо того, чтобы создавать их.
Эти шаблоны позволяют масштабировать код, поскольку предоставляют понятные шаблоны для управления зависимостями без дублирования кода и усложнения. Кроме того, эти шаблоны позволяют быстро переключаться между тестовой и рабочей реализациями.
Общие рекомендации
Программирование — творческая сфера, и создание приложений для Android не является исключением. Существует множество способов решения проблемы: можно обмениваться данными между несколькими активностями или фрагментами, извлекать удалённые данные и сохранять их локально для автономного режима или обрабатывать множество других распространённых ситуаций, с которыми сталкиваются нетривиальные приложения.
Хотя следующие рекомендации не являются обязательными, в большинстве случаев их соблюдение делает вашу кодовую базу более надежной, тестируемой и поддерживаемой в долгосрочной перспективе:
Не храните данные в компонентах приложения.
Избегайте назначения точек входа вашего приложения, таких как действия, сервисы и приёмники широковещательных данных, источниками данных. Вместо этого они должны координироваться с другими компонентами только для получения подмножества данных, релевантных этой точке входа. Срок службы каждого компонента приложения довольно кратковременный и зависит от взаимодействия пользователя с устройством и общего текущего состояния системы.
Уменьшите зависимости от классов Android.
Компоненты вашего приложения должны быть единственными классами, которые полагаются на API SDK фреймворка Android, такие как Context
или Toast
. Абстрагирование от них других классов в вашем приложении улучшает тестируемость и снижает связанность внутри приложения.
Определите четкие границы ответственности между модулями вашего приложения.
Например, не распределяйте код, загружающий данные из сети, по нескольким классам или пакетам в кодовой базе. Аналогично, не определяйте несколько не связанных между собой функций, таких как кэширование и привязка данных, в одном классе. Следование рекомендуемой архитектуре приложения поможет вам в этом.
Раскрывайте как можно меньше информации из каждого модуля.
Например, не поддавайтесь искушению создать сокращение, раскрывающее внутреннюю реализацию модуля. В краткосрочной перспективе вы можете выиграть немного времени, но затем, по мере развития кодовой базы, вы, вероятно, многократно увеличите технический долг.
Сосредоточьтесь на уникальной сути вашего приложения, чтобы оно выделялось среди других приложений.
Не изобретайте велосипед, снова и снова переписывая один и тот же шаблонный код. Вместо этого сосредоточьте время и силы на том, что делает ваше приложение уникальным, а библиотеки Jetpack и другие рекомендуемые библиотеки позаботятся о повторяющемся шаблонном коде.
Используйте канонические макеты и шаблоны дизайна приложений.
Библиотеки Jetpack Compose предоставляют надежные API для создания адаптивных пользовательских интерфейсов. Используйте канонические макеты в своем приложении, чтобы улучшить пользовательский опыт на устройствах разных форм-факторов и размеров экранов. Ознакомьтесь с галереей шаблонов дизайна приложений, чтобы выбрать макеты, которые лучше всего подходят для ваших вариантов использования.
Подумайте, как сделать так, чтобы каждую часть вашего приложения можно было тестировать изолированно.
Например, наличие чётко определённого API для извлечения данных из сети упрощает тестирование модуля, сохраняющего эти данные в локальной базе данных. Если же вместо этого объединить логику этих двух модулей в одном месте или распределить сетевой код по всей кодовой базе, эффективное тестирование станет гораздо сложнее, а то и вовсе невозможно.
Типы отвечают за свою политику параллелизма.
Если тип выполняет длительную блокирующую работу, он должен отвечать за перенос этого вычисления в нужный поток. Этот конкретный тип знает тип выполняемого им вычисления и поток, в котором оно должно выполняться. Типы должны быть безопасными для основного потока, то есть их можно безопасно вызывать из основного потока, не блокируя его.
Сохраняйте как можно больше актуальных и свежих данных.
Таким образом, пользователи смогут пользоваться вашим приложением, даже когда их устройство находится в автономном режиме. Помните, что не все ваши пользователи имеют постоянное высокоскоростное подключение, и даже если оно есть, в людных местах качество связи может быть плохим.
Преимущества архитектуры
Реализация хорошей архитектуры в вашем приложении дает множество преимуществ проектным и инженерным группам:
- Это повышает удобство обслуживания, качество и надежность всего приложения.
- Это позволяет масштабировать приложение. Больше людей и больше команд могут работать над одной и той же кодовой базой, минимизируя конфликты кода.
- Это помогает в адаптации. Поскольку архитектура обеспечивает единообразие в вашем проекте, новые члены команды могут быстро освоиться и работать эффективнее за меньшее время.
- Легче тестировать. Хорошая архитектура поощряет использование более простых типов, которые, как правило, легче тестировать.
- Ошибки можно исследовать методично, используя четко определенные процессы.
Инвестиции в архитектуру также оказывают прямое влияние на ваших пользователей. Они получают более стабильное приложение и больше функций благодаря более продуктивной работе команды разработчиков. Однако архитектура также требует первоначальных затрат времени. Чтобы помочь вам оправдать эти затраты перед остальной частью вашей компании, ознакомьтесь с этими примерами успеха других компаний, использующих хорошую архитектуру в своих приложениях.
Образцы
Следующие примеры от Google демонстрируют хорошую архитектуру приложений. Ознакомьтесь с ними, чтобы увидеть это руководство на практике:
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Уровень данных
- Уровень пользовательского интерфейса
- События пользовательского интерфейса