Основные UI паттерны разработки Android приложений. Подробный гайд по разработке Android-приложений с помощью Clean Architecture

  • Перевод

В этой статье мы рассмотрим архитектуру Android-приложений.

Откровенно говоря, официальную Google по этой теме я считаю не очень полезной. Детально отвечая на вопрос «как», она совсем не объясняет «что» и «почему». Итак, вот моя версия, и, я надеюсь, она внесёт некоторую ясность. Да, кстати, я полностью одобряю чтение статей Google, поскольку они содержат полезную информацию, повторять которую я не собираюсь.

Архитектура ОС Android - немного истории

Как это часто бывает в IT, многие вещи не могут быть объяснены в отрыве от истории возникновения конкретного программного обеспечения. Вот почему мы должны обратиться к истокам ОС Android.

Разработка ОС Android была начата в 2003 молодой компанией Android Inc. В 2005 году эта компания была куплена Google. Я считаю, что главные особенности архитектуры Android были определены именно в этот период. Это заслуга не только Android Inc; архитектурные концепции и финансовые ресурсы Google оказали решающее влияние на архитектуру Android. Далее я приведу несколько примеров.

Если вы помните, 2003-2005 года были ознаменованы повышенным вниманием к AJAX приложениям. Я думаю, это оказало основополагающее влияние на архитектуру Android: во многих аспектах она ближе к архитектуре типичного AJAX приложения, нежели к десктопному GUI приложению, написанному на Java, C#, C++, VB и тп.

Не знаю, почему так произошло. Моя догадка - это придумал кто-то из Google в тот период, когда насыщенные интернет-приложения (Rich Internet Applications, RIA) в духе Google Docs или Gmail считались решением всех проблем. По-моему, эту идею нельзя назвать ни плохой, ни хорошей. Просто помните, что Android-приложения очень сильно отличаются от десктопных.

Влияние архитектурной философии Eclipse заметно в выборе принципа реализации GUI, который больше похоже на SWT, нежели на Swing.

В стандартах оформления кода Android присутствует «венгерская нотация», рождённая в стенах MS. Можно предположить, что тот, кто писал эти стандарты, ранее занимался разработкой под Windows.

Архитектурные уровни Android
Операционная система Android имеет три весьма различных и сильно отделённых друг от друга уровня:
  1. В основе лежит модифицированная и урезанная версия Linux, как я и упоминал в одной из моих предыдущих статей .
  2. Над уровнем Linux находится уровень инфраструктуры приложения, содержащий виртуальную машину Dalvik , веб-браузер, базу данных SQLite , некие инфраструктурные «костыли» и Java API.
  3. И, наконец, уровень написанных в Google Android-приложений. Вообще говоря, они являются расширением уровня инфраструктуры, поскольку разработчик может использовать эти приложения или их части как строительные блоки для собственных разработок.
Рассмотрим эти слои один за другим и более подробно.

Уровень Linux

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

Грубо говоря, у вас два пути: реализовывать собственные идеи, начав с нуля или же использовать существующую ОС и адаптировать её под свои устройства.

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

Тем не менее, это не всегда практично. Например, использование ядра Linux заметно уменьшило стоимость разработки (возможно где-то и без того чрезмерно большую). Согласитесь, если кто-то решит создать нечто, напоминающее ядро Linux в его сегодняшнем состоянии, ему потребуется несколько миллионов долларов.

Если вы руководите Android Inc, то у вас по определению не может быть столько денег. Если вы руководите Google, то у вас такие деньги найдутся, но вы, скорее всего, подумаете дважды, прежде чем потратить их на создание собственной ОС. Так же вы потратите несколько лет, прежде чем достигните сегодняшнего состояния Linux; несколько лет задержки могут стать слишком большим опозданием при выходе на рынок.

В подобной ситуации компания Apple решила построить Mac OS на основе Free BSD. Android Inc приняла решение использовать Linux как основу для Android. Исходники как Free BSD, так и Linux, находятся в свободном доступе и предоставляют собой хорошую основу для любых разработок, будь то Apple или Google.

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

Если рассматривать Linux на высоком уровне, то это комбинация ядра (без которого нельзя обойтись) и множества других, необязательных частей. Можно даже запустить одно ядро, без чего бы то ни было ещё. Так, Google вынуждена в любом случае использовать ядро Linux как часть ОС Android. Кроме того, были рассмотрены необязательные части и из них выбрано самое необходимое. Например, были добавлены сетевой фаервол IPTables и оболочка Ash. Любопытно, что добавили именно Ash, а не Bash, не смотря на то, что последний на порядок мощнее; вероятно, это решение было основано на том, что Ash менее требователен к ресурсам.

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

Выбор Linux в качестве основы оказал огромное влияние на все аспекты ОС Android. Сборка Android, по сути, есть вариация процесса сборки Linux. Код Android находится под управлением git (инструмент, разработанный для управления кодом Linux). И так далее.

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

Вы можете спросить, как же быть, если необходимо разработать нативное приложение для Android? Google настоятельно не рекомендует делать этого. Технически, конечно, это возможно, но в дальнейшем у вас не будет возможности распространять это приложение нормальным способом. Так что подумайте дважды, прежде чем начать нативную разработку под Android, если конечно, вы не работает над Android Open Source Project (AOSP), т.е. собственно ОС Android.

Уровень инфраструктуры приложения

Несмотря на некоторое сходство Apple iOS и Android ОС, существуют значительные отличия между архитектурными решениями на инфраструктурном уровне обоих ОС.

Apple решила использовать Objective-C как язык программирования и среду выполнения приложения iOS. Objective-C выглядит более или менее естественным выбором для ОС, в основе которой лежит Free BSD. Можно рассматривать Objective-C как обычный C++ с кастомным препроцессором, который добавляет некоторые специфические лингвистические конструкции. Почему же нельзя использовать стандартный C++, на котором написана Free BSD? Мне кажется причина в том, что Apple старается всё делать в своём, «эппловском» стиле.

Основная идея в том, что приложения iOS написаны более или менее на том же языке, что и стоящая за ними ОС.

Android-приложения сильно отличаются в этом смысле. Они написаны на Java, а это совсем другая технология, нежели C++ (хотя синтаксис и унаследован от C++).

Я думаю, основная причина состоит в необходимости одному и тому же приложению работать на различном аппаратном обеспечении. Эта проблема имеет место лишь для ОС Android; у ребят из Apple такой проблемы нет. iOS работает только на оборудовании собственного производства, и Apple полностью контролирует весь процесс. Для Android же всё наоборот: Google не контролирует производителей аппаратных средств. Например, ОС Android работает на процессорах с архитектурой x86, ARM и Atom (в комментах подсказывают, что x86 включает в себя Atom, и Android работает на x86, ARM, PPC и MIPS - примечание переводчика ). На бинарном уровне эти архитектуры несовместимы.

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

Для того, чтобы одно и то же приложение могло работать на разном аппаратном обеспечении, компания Google использовала контейнер-ориентированную архитектуру (container-based architecture). В такой архитектуре двоичный код выполняется программным контейнером и изолируется от деталей конкретного аппаратного обеспечения. Примеры всем знакомы - Java и C#. В обоих языках двоичный код не зависит от специфики аппаратного обеспечения и выполняется виртуальной машиной.

Конечно, есть и другой способ достигнуть независимости от аппаратного обеспечения на уровне двоичного кода. Как один из вариантов, можно использовать эмулятор аппаратного обеспечения, так же известный как QEMU . Он позволяет эмулировать, например, устройство с процессором ARM на платформе x86 и так далее. Google могла бы использовать C++ как язык для разработки приложений внутри эмуляторов. Действительно, Google использует такой подход в своих эмуляторах Android, которые построены на основе QEMU.

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

Как бы то ни было, компания Google пришла к решению использовать Java как основной язык разработки приложений и среды их выполнения.

Я думаю, это было критически важное архитектурное решение, которое поставило Android в стороне от остальных мобильных ОС на основе Linux, представленных в настоящее время. Насколько мне известно, ни у одной из них нет совместимости двоичного кода на уровне приложений. Возьмём для примера MeeGo . Она использует C++ и фреймворк Qt ; не смотря на то, что Qt кроссплатформенный, необходимость делать разные сборки для разных платформ не исчезает.

Выбрав Java, нужно было решить, какую виртуальную машину (JVM) использовать. Ввиду ограниченности ресурсов использование стандартной JVM было затруднено. Единственным возможным выбором было использование Java ME JVM, разработанной для мобильных устройств. Однако счастье Google было бы неполным без разработки собственной виртуальной машины, и появилась Dalvik VM .

Dalvik VM отличается от других виртуальных Java-машин следующим:

  • Она использует специальный формат DEX для хранения двоичных кодов, в противовес форматам JAR и Pack200, которые являются стандартом для других виртуальных Java-машинах. Компания Google заявила, что бинарники DEX меньше, чем JAR. Я думаю, с тем же успехом они могли бы использовать Pack200, но они решили пойти своим путём.
  • Dalvik VM оптимизирована для выполнения нескольких процессов одновременно.
  • Dalvik VM использует архитектуру, основанную на регистрах против стековой архитектуры в других JVM, что приводит к увеличению скорости выполнения и уменьшению размеров бинарников.
  • Она использует собственный набор инструкций (а не стандартный байткод JVM)
  • Возможен запуск (если необходимо) нескольких независимых Android-приложений в одном процессе
  • Выполнение приложения может охватывать несколько процессов Dalvik VM «естественным образом» (позже мы обсудим, что это значит). Для поддержи этого добавлено:
    • Специальный механизм сериализации объектов, основанный на классах Parcel и Parcelable. Функционально преследуются те же цели, что и Java Serializable, но в результате данные имеют меньший объём и потенциально более терпимы к версионным изменениям классов.
    • Особый способ для выполнения вызовов между процессами (inter process calls, IPC), основный на Android Interface Definition Language (AIDL).
  • До Android 2.2 Dalvik VM не поддерживала JIT-компиляцию, что было серьёзным ударом по производительности. Начиная с версии 2.2, скорость выполнения часто используемых приложений

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

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

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

Что такое Clean Architecture?

Я не собираюсь вдаваться в подробности, потому что есть статьи, в которых это объясняется лучше, чем смогу сделать я. Однако в следующем абзаце рассматривается ключевой вопрос, который вам необходимо знать, чтобы понять, как устроен подход Clean Architecture.

Как правило, в Clean Architecture код разделен на несколько уровней, по структуре схожей со структурой обычного лука, с одним правилом зависимости : внутренний уровень не должен зависеть от каких-либо внешних уровней. Это означает, что зависимости должны указываться внутри каждого уровня, чтобы не было зависимостей между уровнями (слоями) .

Clean Architecture, делает ваш код:
  • Независящим от фреймворков;
  • Тестируемым;
  • Независящим от UI;
  • Независящим от Базы данных;
  • Независимым от какого-либо внешнего воздействия.

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

Что это значит для Android?

Как правило, ваше приложение имеет произвольное количество уровней (слоев), однако если вам не нужна бизнес-логика Enterprise, то скорее всего у вас будет только 3 уровня:

  • Внешний: Уровень реализации;
  • Средний: Уровень интерфейса;
  • Внутренний: Уровень бизнес-логики.

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

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

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

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

Почему преобразование является обязательным?

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

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

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

Как начать создание Чистых приложений?

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

Первые шаги по написанию новых прецедентов

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

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

Структура

Общая структура Android-приложения выглядит, как показано ниже:

  • Пакеты внешнего уровня: Интерфейс пользователя, хранилище, сеть и т.д.;
  • Пакеты среднего уровня: Представители , конвертеры;
  • Пакеты внутреннего уровня: Interactors, модели, репозитории, исполнители.

Внешний уровень

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

Интерфейс пользователя (UI ) – Это то, куда вы помещаете все ваши Операции, Фрагменты, Адаптеры и любой другой Android-код, связанный с интерфейсом пользователя.

Хранилище – Отдельный код для базы данных, который реализует интерфейс наших Интеракторов, используемых для доступа к базе данных и для хранения данных. Например, сюда включается Поставщик контента или ORM-ы такие, как DBFlow .

Исполнитель (executor ) – данный пакет содержит код для запуска Interactor-классов в фоновом режиме с помощью рабочего потока-исполнителя. Чаще всего вам не придется изменять этот пакет.

Простой пример

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

  • Пакет представления ;
  • Пакет хранилища ;
  • Пакет домена .

Первые два пункта относятся к внешнему уровню, в то время как последний относится к внутреннему/основному уровню.

Пакет представления ответственен за все, что связано с отображением вещей на экране, он содержит весь стек шаблона проектирования MVP . Это означает, что он содержит в себе как UI, так и Presenter-пакеты, даже если они относятся к разным уровням.

Отлично – меньше слов, больше кода!

Создание нового Interactor-а (внутренний/основной уровень)

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

Итак, давайте начнем создание Interactor-а. Interactor – это то место, где располагается основная логика работы нашего прецедента. Все Interactor -ы запускаются в фоновом потоке, поэтому не должно быть никакого воздействия на производительность интерфейса пользователя . Давайте создадим новый Interactor с приятным названием «WelcomingInteractor ».

public interface WelcomingInteractor extends Interactor { interface Callback { void onMessageRetrieved(String message); void onRetrievalFailed(String error); } }

public interface WelcomingInteractor extends Interactor {

interface Callback {

void onMessageRetrieved (String message ) ;

void onRetrievalFailed (String error ) ;

Callback отвечает за общение с интерфейсом пользователя (UI) в основном потоке, мы помещаем его в интерфейс Interactor-а, поэтому нет необходимости в подобном названии «WelcomingInteractorCallback», чтобы отличать его от других callback-ов. Теперь реализуем логику получения сообщения. Давайте скажем, что у нас есть интерфейс MessageRepository , в котором будет наше сообщение приветствия.

public interface MessageRepository { String getWelcomeMessage(); }

public interface MessageRepository {

String getWelcomeMessage () ;

public class WelcomingInteractorImpl extends AbstractInteractor implements WelcomingInteractor { ... private void notifyError() { mMainThread.post(new Runnable() { @Override public void run() { mCallback.onRetrievalFailed("Nothing to welcome you with:("); } }); } private void postMessage(final String msg) { mMainThread.post(new Runnable() { @Override public void run() { mCallback.onMessageRetrieved(msg); } }); } @Override public void run() { // получение сообщения final String message = mMessageRepository.getWelcomeMessage(); // проверяем, получили ли мы сообщение if (message == null || message.length() == 0) { // уведомляем об ошибке основной поток notifyError(); return; } // мы получили наше сообщение, уведомляем об этом UI в основном потоке postMessage(message); }

public class WelcomingInteractorImpl extends AbstractInteractor implements WelcomingInteractor {

. . .

private void notifyError () {

@ Override

public void run () {

mCallback . onRetrievalFailed ("Nothing to welcome you with:(" ) ;

} ) ;

private void postMessage (final String msg ) {

mMainThread . post (new Runnable () {

@ Override

public void run () {

mCallback . onMessageRetrieved (msg ) ;

} ) ;

@ Override

public void run () {

// получение сообщения

Final String message = mMessageRepository . getWelcomeMessage () ;

// проверяем, получили ли мы сообщение

if (message == null || message . length () == 0 ) {

// уведомляем об ошибке основной поток

notifyError () ;

return ;

// мы получили наше сообщение, уведомляем об этом UI в основном потоке

postMessage (message ) ;

Что же, взглянем на зависимости, создаваемые нашим Interactor:Этот фрагмент кода, пытается получить сообщение, затем переслать его или же отправить сообщение об ошибке интерфейсу пользователя, чтобы он отобразил сообщение или ошибку. Для этого мы уведомляем интерфейс пользователя с помощью нашего callback-а, который по факту и будет Presenter-ом. Собственно, в этом и заключается суть всей нашей бизнес-логики . Все что нам остается – это построить структурные зависимости.

import com.kodelabs.boilerplate.domain.executor.Executor; import com.kodelabs.boilerplate.domain.executor.MainThread; import com.kodelabs.boilerplate.domain.interactors.WelcomingInteractor; import com.kodelabs.boilerplate.domain.interactors.base.AbstractInteractor; import com.kodelabs.boilerplate.domain.repository.MessageRepository;

import com . kodelabs . boilerplate . domain . executor . Executor ;

import com . kodelabs . boilerplate . domain . executor . MainThread ;

import com . kodelabs . boilerplate . domain . interactors . WelcomingInteractor ;

import com . kodelabs . boilerplate . domain . interactors . base . AbstractInteractor ;

import com . kodelabs . boilerplate . domain . repository . MessageRepository ;

Как вы можете заметить, здесь нет ни одного упоминания о каком-либо Android -коде . Это и есть главное преимущество данного подхода. Также вы можете увидеть, что пункт: «Независимость от фреймворков» все также соблюдается. Кроме того, нам не нужно отдельно определять интерфейс пользователя или базу данных, мы просто вызываем методы интерфейса, которые кто-то, где-то на внешнем уровне реализует. Следовательно, мы независим от UI и независим от Базы данных .

Тестирование нашего Interactor-а

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

... @Test public void testWelcomeMessageFound() throws Exception { String msg = "Welcome, friend!"; when(mMessageRepository.getWelcomeMessage()) .thenReturn(msg); WelcomingInteractorImpl interactor = new WelcomingInteractorImpl(mExecutor, mMainThread, mMockedCallback, mMessageRepository); interactor.run(); Mockito.verify(mMessageRepository).getWelcomeMessage(); Mockito.verifyNoMoreInteractions(mMessageRepository); Mockito.verify(mMockedCallback).onMessageRetrieved(msg); }

. . .

@ Test

public void testWelcomeMessageFound () throws Exception {

String msg = "Welcome, friend!" ;

when (mMessageRepository . getWelcomeMessage () )

ThenReturn (msg ) ;

WelcomingInteractorImpl interactor = new WelcomingInteractorImpl (

mExecutor ,

mMainThread ,

mMockedCallback ,

mMessageRepository

interactor . run () ;

Mockito . verify (mMessageRepository ) . getWelcomeMessage () ;

Mockito . verifyNoMoreInteractions (mMessageRepository ) ;

Mockito . verify (mMockedCallback ) . onMessageRetrieved (msg ) ;

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

Создание уровня представления

Код представления относится ко внешнему уровню подхода Clean Architecture. Уровень представления состоит из структурно зависимого кода, который отвечает за отображение интерфейса пользователя, собственно, пользователю. Мы будем использовать класс MainActivity для отображения нашего приветствующего сообщения пользователю, когда приложение возобновляет свою работу.

Давайте начнем с создания интерфейса нашего Presenter и Отображения (View ). Единственное, что должно делать наше отображение – это отображать приветствующее сообщение:

public interface MainPresenter extends BasePresenter { interface View extends BaseView { void displayWelcomeMessage(String msg); } }

public interface MainPresenter extends BasePresenter {

interface View extends BaseView {

void displayWelcomeMessage (String msg ) ;

Итак, как и где мы запускаем наш Interactor, когда приложение возобновляет работу? Все, что не имеет строгой привязки к отображению, должно помещаться в класс Presenter. Это помогает достичь принципа Разделения ответственности и предотвратить классы Операций от чрезмерного увеличения размера кода. Сюда включается весь код, который работает с Interactor-ми.

В нашем классе MainActivity мы переопределяем метод onResume() :

@Override protected void onResume() { super.onResume(); // начнем возврат приветствующего сообщения, при возобновлении работы приложения mPresenter.resume(); }

Все Presenter-объекты реализуют метод resume() , при наследовании BasePresenter .

Примечание : Самые внимательные читатели могли заметить, что я добавил Android-методы жизненного цикла в интерфейс BasePresenter в качестве вспомогательных методов, хотя сам Presenter находится на более низком уровне. Наш Presenter должен знать все на уровне UI, к примеру, что что-то на этом уровне имеет жизненный цикл. Тем не менее, здесь я не указываю конкретное событие , так как каждый UI для конкретного пользователя может отрабатывать разные события, в зависимости от действий пользователя. Представьте, я назвал его onUIShow() вместо onResume() . Теперь все хорошо, верно? 🙂

Мы запускаем Interactor внутри класса MainPresenter в методе resume() :

@Override public void resume() { mView.showProgress(); // инициализируем interactor WelcomingInteractor interactor = new WelcomingInteractorImpl(mExecutor, mMainThread, this, mMessageRepository); // запускаем interactor interactor.execute(); }

@ Override

public void resume () { mView . showProgress () ; // инициализируем interactor

WelcomingInteractor interactor = new WelcomingInteractorImpl (

mExecutor ,

mMainThread ,

this ,

mMessageRepository

) ; // запускаем interactor

interactor . execute () ;

Метод execute () просто выполняет метод run () объекта WelcomingInteractorImpl в фоновом потоке. Метод run () вы можете увидеть в разделе Создание нового Interactor .

Вы также могли заметить, что поведение Interactor-а схоже с поведением класса AsyncTask . Так как вы предоставляете все необходимое для его запуска и выполнения. Тут вы можете спросить, а почему мы не используем AsyncTask ? Да потому что это Android-код, и вам нужен будет эмулятор для его запуска и тестирования.

Мы предоставляем несколько вещей нашему Interactor-у:

  • Экземпляр ThreadExecutor , который отвечает за выполенение Interactor-а в фоновом потоке. Я чаще всего создаю его как singleton. Этот класс также располагается внутри domain-пакета и нет необходимости реализовывать его во внешнем уровне;
  • Экземпляр MainThreadImpl , который отвечает за отправку запущенных потоков Interactor-а в главный поток приложения. Основные потоки имеют доступ к использованию определённого структурного кода и поэтому мы должны реализовывать их во внешнем уровне;
  • Также вы могли обратить внимание на то, что мы предоставляем this нашему Interactor-у. MainPresenter – это callback-объект, который используется Interactor-ом для уведомления UI о каких-либо событиях;
  • Кроме того, мы предоставляем экземпляр WelcomeMessageRepository , который отвечает за реализацию интерфейса MessageRepository , который в свою очередь использует Interactor. WelcomeMessageRepository будет рассмотрен позже, в разделе Создание уровня хранения .

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

Что же касается this , то MainPresenter класса MainActivity действительно реализует callback-интерфейс:

public class MainPresenterImpl extends AbstractPresenter implements MainPresenter, WelcomingInteractor.Callback {

public class MainPresenterImpl extends AbstractPresenter implements MainPresenter , WelcomingInteractor . Callback {

@Override public void onMessageRetrieved(String message) { mView.hideProgress(); mView.displayWelcomeMessage(message); } @Override public void onRetrievalFailed(String error) { mView.hideProgress(); onError(error); }

Название Описание Необходимость
gen Файлы, сгенерированные самой Java. Здесь находится такой важный файл как R.java Да
AndroidManifest.xml Файл манифеста AndroidManifest.xml предоставляет системе основную информацию о программе. Каждое приложение должно иметь свой файл манифеста Да
src Каталог, в котором содержится исходный код приложения Да
assets Произвольное собрание каталогов и файлов Нет
res Каталог, содержащий ресурсы приложения. В данном каталоге могут находиться подпапки drawable, anim, layout, menu, values, xml и raw (см. ниже) Да

1.5.1. Файл манифеста AndroidManifest.xml

Файл манифеста AndroidManifest.xml предоставляет системе основную информацию о программе. Каждое приложение должно иметь свой файл AndroidManifest.xml. Редактировать файл манифеста можно вручную, изменяя XML-код или через визуальный редактор Manifest Editor, который позволяет осуществлять визуальное и текстовое редактирование файла манифеста приложения.

Назначение файла:

  • описывает компоненты приложения – Activities, Services, Broadcast receivers и Content providers;
  • содержит список необходимых разрешений для обращения к защищенным частям API и взаимодействия с другими приложениями;
  • объявляет разрешения, которые сторонние приложения обязаны иметь для взаимодействия с компонентами данного приложения;
  • объявляет минимальный уровень API Android, необходимый для работы приложения;
  • перечисляет связанные библиотеки.

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

  • является корневым элементом манифеста.

    По умолчанию Eclipse создает элемент с четырьмя атрибутами:

    xmlns:android определяет пространство имен Android.

    package определяет уникальное имя пакета приложения.

    android:versionCode указывает на внутренний номер версии.

    android:versionName указывает номер пользовательской версии.

  • Объявляет разрешение, которое используется для ограничения доступа к определенным компонентам или функциональности данного приложения. В этой секции описываются права, которые должны запросить другие приложения для получения доступа к приложению. Приложение может также защитить свои собственные компоненты (Activities, Services, Broadcast receivers и Content providers) разрешениями. Оно может использовать любое из системных разрешений, определенных Android или объявленных другими приложениями, а также может определить свои собственные разрешения.
  • запрашивает разрешения, которые приложению должны быть предоставлены системой для его нормального функционирования. Разрешения предоставляются во время установки приложения, а не во время его работы.

    Наиболее распространненные разрешения:

    INTERNET – доступ к интернету

    READ_CONTACTS – чтение (но не запись) данных из адресной книги пользователя

    WRITE_CONTACTS – запись (но не чтение) данных в адресную книгу пользователя

    RECEIVE_SMS – обработка входящих SMS

    ACCESS_FINE_LOCATION – точное определение местонахождения при помощи GPS

  • позволяет объявлять совместимость приложения с указанной версией (или более новыми версиями API) платформы Android. Уровень API, объявленный приложением, сравнивается с уровнем API системы мобильного устройства, на который инсталлируется данное приложение.

    Атрибуты:

    android:minSdkVersion определяет минимальный уровень API, требуемый для работы приложения. Система Android будет препятствовать тому, чтобы пользователь установил приложение, если уровень API системы будет ниже, чем значение, определенное в этом атрибуте.

    android:maxSDKVersion позволяет определить самую позднюю версию, которую готова поддерживать программа.

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

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

    Возможные атрибуты:

    android.hardware.camera – требуется аппаратная камера.

    android.hardware.camera.autofocus – требуется камера с автоматической фокусировкой.

  • определяет разрешение экрана, требуемое для функционирования приложения. По умолчанию современное приложение с уровнем API 4 или выше поддерживает все размеры экрана и должно игнорировать этот элемент.
  • один из основных элементов манифеста, содержащий описание компонентов приложения. Содержит дочерние элементы ( , , , И другие), которые объявляют каждый из компонентов, входящих в состав приложения. В манифесте может быть только один элемент .

1.5.2. Ресурсы

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

В основном, ресурсы хранятся в виде XML-файлов в каталоге res с подкаталогами values, drawable-ldpi, drawable-mdpi, drawable-hdpi, layout. Но также бывают еще два типа ресурсов: raw и assets.

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

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

Самыми распространенными ресурсами являются, пожалуй, строки (string), цвета (color) и графические рисунки (bitmap).

В следующей таблице перечислены основные ресурсы Android-приложения:

Тип ресурса Размещение Описание
Цвета /res/colors/ Идентификатор цвета, указывающий на цветовой код.
Строки /res/strings/ Строковые ресурсы. В их число также входят строки в формате java и html.
Меню /res/menus/ Меню в приложении можно задать как XML-ресурсы.
Параметры /res/values/ Представляет собой параметры или размеры различных элементов.
Изображения /res/drawable/ Ресурсы-изображения. Поддерживает форматы JPG, GIF, PNG (самый предпочтительный) и другие. Каждое изображение является отдельным файлом. Система также поддерживает stretchable images, в которых можно менять масштаб отдельных элементов, а другие элементы оставлять без изменений.

Отрисовываемые цвета

/res/values/

/res/drawable/

Представляет цветные прямоугольники, которые используются в качестве фона основных отрисовываемых объектов, например точечных рисунков.
Анимация /res/anim/ Android может выполнить простую анимацию на графике или на серии графических изображений.
Произвольные XML-файлы /res/xml/ В Android в качестве ресурсов могут использоваться произвольные XML-файлы.
Произвольные необработанные ресурсы /res/raw/ Любые нескомпилированные двоичные или текстовые файлы, например, видео.

Помимо изображений в каталоге res/drawable могут храниться ресурсы простых геометрических фигур. Вот лишь некоторые из возможных атрибутов:

  • android:shape задает тип фигуры: rectangle (прямоугольник), oval (овал), line (линия), ring (окружность);
  • создает закругленные углы для прямоугольника;
  • задает градиентную заливку для фигуры; в Android можно создавать три типа градиентов: Linear (линейный), Radial (радиальный) и Sweep (разверточный);
  • задает размеры фигуры;
  • задает сплошной цвет для фигуры.

Анимация в Android бывает двух видов:

  • Frame Animation – кадровая анимация, традиционная анимация при помощи быстрой смены последовательных изображений, как на кинопленке.
  • Tween Animation – анимация преобразований может выполняться в виде ряда простых преобразований: изменение позиции (класс TranslateAnimation), размера (ScaleAnimation), угла вращения (RotateAnimation) и уровня прозрачности (AlphaAnimation). Команды анимации определяют преобразования, которые необходимо произвести над объектом. Преобразования могут быть последовательными или одновременными. Последовательность команд анимации определяется в XML-файле (предпочтительно) или в программном коде.

В Android имеется еще один каталог, в котором моrут храниться файлы, предназначенные для включения в пакет – /assets . Это не ресурсы, а просто необработанные файлы. Этот каталог находится на том же уровне, что и /res. Для файлов, располагающихся в /assets, в R.java не генерируются идентификаторы ресурсов. Для их считывания необходимо указать путь к файлу. Путь к файлу является относительным и начинается с /assets. Этот каталог, в отличие от подкаталога res/, позволяет задавать произвольную глубину подкаталогов и произвольные имена файлов.

1.5.3. Разметка

В Android-приложениях, пользовательский интерфейс построен на View и ViewGroup объектах. Класс ViewGroup является основой для подкласса Layout (разметка).

Разметка (также используются термины компоновка или макет) хранится в виде XML-файла в папке /res/layout . Это сделано для того, чтобы отделить код от дизайна, как это принято во многих технологиях (HTML и CSS, Visual Studio и Expression Blend). Кроме основной компоновки для всего экрана, существуют дочерние компоновки для группы элементов. По сути, компоновка – это некий визуальный шаблон для пользовательского интерфейса приложения, который позволяет управлять элементами, их свойствами и расположением. В своей практике вам придется познакомиться со всеми способами размещения.

Android-плагин для Eclipse включает в себя специальный редактор для создания разметки двумя способами. Редактор имеет две вкладки: одна позволяет увидеть, как будут отображаться элементы управления, а вторая – создавать XML-разметку вручную.

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

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

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

  • FrameLayout является самым простым типом разметки. Обычно это пустое пространство на экране, которое можно заполнить только дочерним объектом View или ViewGroup . Все дочерние элементы FrameLayout прикрепляются к верхнему левому углу экрана. В разметке FrameLayout нельзя определить различное местоположение для дочернего объекта View. Последующие дочерние объекты View будут просто рисоваться поверх предыдущих представлений, частично или полностью затеняя их, если находящийся сверху объект непрозрачен
  • LinearLayout выравнивает все дочерние объекты в одном направлении – вертикально или горизонтально. Направление задается при помощи атрибута ориентации android:orientation . Все дочерние элементы помещаются в стек один за другим, так что вертикальный список представлений будет иметь только один дочерний элемент в строке независимо от того, насколько широким он является. Горизонтальное расположение списка будет размещать элементы в одну строку с высотой, равной высоте самого высокого дочернего элемента списка.
  • TableLayout позиционирует свои дочерние элементы в строки и столбцы. TableLayout не отображает линии обрамления для рядов, столбцов или ячеек. TableLayout может иметь ряды с разным количеством ячеек. При формировании разметки таблицы некоторые ячейки при необходимости можно оставлять пустыми. TableLayout удобно использовать, например, при создании логических игр типа Судоку, Крестики-Нолики и тому подобных.
  • RelativeLayout позволяет дочерним элементам определять свою позицию относительно родительского представления или относительно соседних дочерних элементов.

Все описываемые разметки являются подклассами ViewGroup и наследуют свойства, определенные в классе View.

Разметки ведут себя как элементы управления, и их можно группировать. Расположение элементов управления может быть вложенным. Например, можно использовать RelativeLayout в LinearLayout и так далее. Однако, слишком большая вложенность элементов управления вызывает проблемы с производительностью.

Поскольку разработка приложений под Android набирает популярность, думаю обзор основных UI паттернов для Android-приложений будет кому-то полезен. Основой для статьи является вот этот вот источник. Рассматриваемые паттерны: Dashboard, Action Bar, Quick Actions, Search Bar и Companion Widget.

На мой взгляд тема UI паттернов является важной по нескольким причинам:

  1. Привлечение пользователей: паттерны помогают сделать приложение более юзабильным, более понятным.
  2. Проход на рынок: следование паттернам может сыграть важную роль при продвижении приложения на app market’ы.
  3. Не стоит строить велосипед: при знании паттернов намного проще заниматься проектированием интерфейса приложения, используя имеющиеся решения.
Принципы дизайна интерфейса, отмеченные инженерами Google:
  • Simple vs Clear : интерфейс должен быть простым(не нагруженным) и понятным для использования
  • Content vs Chrome : необходимо использовать максимум экрана, при этом уменьшать его визуальную сложность (использовать ограниченное число кнопок/иконок)
  • Consistent yet engaging : консистентность реакции пользователя – пользователь должен понимать что он делает/как сделать то, что ему необходимо
  • Enhanced by cloud : данные пользователя следует хранить в облаке; пользователь должен иметь возможность выбирать настройки(организовывать данные) один раз, без повторных действий.
UI Design Patterns (по аналогии с Software Design Patterns) описывают общее решение для повторно возникаемых задач/проблем и возникают как “побочный продукт” процесса разработки.

Ниже перечислены пять UI паттернов с примерами на основе .

DashBoard

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

Action Bar

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

Quick Actions

Quick Actions – предоставляет доступ к контекстуальным функциям приложения, вызывается при клике на ”цели”, выводится на эран в качестве popup. Ключевые характеристики Quick Actions: действия должны соответствовать контексту, быть простыми и понятными(возможно использование иконок), действий не должно быть много. Стоит также отметить, что всплывающий popup не должен перекрывать “цели”(должен появляться либо снизу, либо сверху по отношению к “цели”). Использовать данный паттерн рекомендуется когда нет детального описания item-a, а также когда в приложении необходимо выполнить дополнительные действия, связанные с контекстом. Quick Actions не следует использовать, когда доступен мультиселект.

Search Bar

Search Bar – используется для поиска по приложению (заменяет Action Bar). Search Bar должен поддерживать предложения по поиску, а также может содержать селектор для выбора типа поиска.
Рекомендации по реализации паттерна: следует использовать для простого поиска по приложению, представлять богатые предложения поиска(например, заголовок с иконкой и описанием).
Companion Widget

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

Рассмотренные паттерны являются базовыми при разработке Android приложений, однако это не значит что все их обязательно необходимо применять. Главной все же остается идея, исходя из которой можно рассматривать различные варианты решений(это к тому что, разрабатывать все же стоит от идеи, а не от паттерна).
Удачи вам в реализации ваших идей!

P.S. Если тема вызывет интерес, можно продолжить обзором других UI паттернов.

Android apps can be written using Kotlin, Java, and C++ languages. The Android SDK tools compile your code along with any data and resource files into an APK, an Android package , which is an archive file with an .apk suffix. One APK file contains all the contents of an Android app and is the file that Android-powered devices use to install the app.

Each Android app lives in its own security sandbox, protected by the following Android security features:

  • The Android operating system is a multi-user Linux system in which each app is a different user.
  • By default, the system assigns each app a unique Linux user ID (the ID is used only by the system and is unknown to the app). The system sets permissions for all the files in an app so that only the user ID assigned to that app can access them.
  • Each process has its own virtual machine (VM), so an app"s code runs in isolation from other apps.
  • By default, every app runs in its own Linux process. The Android system starts the process when any of the app"s components need to be executed, and then shuts down the process when it"s no longer needed or when the system must recover memory for other apps.

The Android system implements the principle of least privilege . That is, each app, by default, has access only to the components that it requires to do its work and no more. This creates a very secure environment in which an app cannot access parts of the system for which it is not given permission. However, there are ways for an app to share data with other apps and for an app to access system services:

  • It"s possible to arrange for two apps to share the same Linux user ID, in which case they are able to access each other"s files. To conserve system resources, apps with the same user ID can also arrange to run in the same Linux process and share the same VM. The apps must also be signed with the same certificate.
  • An app can request permission to access device data such as the user"s contacts, SMS messages, the mountable storage (SD card), camera, and Bluetooth. The user has to explicitly grant these permissions. For more information, see .

The rest of this document introduces the following concepts:

  • The core framework components that define your app.
  • The manifest file in which you declare the components and the required device features for your app.
  • Resources that are separate from the app code and that allow your app to gracefully optimize its behavior for a variety of device configurations.

App components

App components are the essential building blocks of an Android app. Each component is an entry point through which the system or a user can enter your app. Some components depend on others.

There are four different types of app components:

  • Activities
  • Services
  • Broadcast receivers
  • Content providers

Each type serves a distinct purpose and has a distinct lifecycle that defines how the component is created and destroyed. The following sections describe the four types of app components.

Activities An activity is the entry point for interacting with the user. It represents a single screen with a user interface. For example, an email app might have one activity that shows a list of new emails, another activity to compose an email, and another activity for reading emails. Although the activities work together to form a cohesive user experience in the email app, each one is independent of the others. As such, a different app can start any one of these activities if the email app allows it. For example, a camera app can start the activity in the email app that composes new mail to allow the user to share a picture. An activity facilitates the following key interactions between system and app:
  • Keeping track of what the user currently cares about (what is on screen) to ensure that the system keeps running the process that is hosting the activity.
  • Knowing that previously used processes contain things the user may return to (stopped activities), and thus more highly prioritize keeping those processes around.
  • Helping the app handle having its process killed so the user can return to activities with their previous state restored.
  • Providing a way for apps to implement user flows between each other, and for the system to coordinate these flows. (The most classic example here being share.)
Content providers A content provider manages a shared set of app data that you can store in the file system, in a SQLite database, on the web, or on any other persistent storage location that your app can access. Through the content provider, other apps can query or modify the data if the content provider allows it. For example, the Android system provides a content provider that manages the user"s contact information. As such, any app with the proper permissions can query the content provider, such as , to read and write information about a particular person. It is tempting to think of a content provider as an abstraction on a database, because there is a lot of API and support built in to them for that common case. However, they have a different core purpose from a system-design perspective. To the system, a content provider is an entry point into an app for publishing named data items, identified by a URI scheme. Thus an app can decide how it wants to map the data it contains to a URI namespace, handing out those URIs to other entities which can in turn use them to access the data. There are a few particular things this allows the system to do in managing an app:
  • Assigning a URI doesn"t require that the app remain running, so URIs can persist after their owning apps have exited. The system only needs to make sure that an owning app is still running when it has to retrieve the app"s data from the corresponding URI.
  • These URIs also provide an important fine-grained security model. For example, an app can place the URI for an image it has on the clipboard, but leave its content provider locked up so that other apps cannot freely access it. When a second app attempts to access that URI on the clipboard,the system can allow that app to access the data via a temporary URI permission grant so that it is allowed to access the data only behind that URI, but nothing else in the second app.

Content providers are also useful for reading and writing data that is private to your app and not shared.

A content provider is implemented as a subclass of and must implement a standard set of APIs that enable other apps to perform transactions. For more information, see the developer guide.

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.

When the system starts a component, it starts the process for that app if it"s not already running and instantiates the classes needed for the component. For example, if your app starts the activity in the camera app that captures a photo, that activity runs in the process that belongs to the camera app, not in your app"s process. Therefore, unlike apps on most other systems, Android apps don"t have a single entry point (there"s no main() function).

Because the system runs each app in a separate process with file permissions that restrict access to other apps, your app cannot directly activate a component from another app. However, the Android system can. To activate a component in another app, deliver a message to the system that specifies your intent to start a particular component. The system then activates the component for you.

Activating components

Three of the four component types—activities, services, and broadcast receivers—are activated by an asynchronous message called an intent . Intents bind individual components to each other at runtime. You can think of them as the messengers that request an action from other components, whether the component belongs to your app or another.

The manifest file

Before the Android system can start an app component, the system must know that the component exists by reading the app"s manifest file , AndroidManifest.xml . Your app must declare all its components in this file, which must be at the root of the app project directory.

The manifest does a number of things in addition to declaring the app"s components, such as the following:

  • Identifies any user permissions the app requires, such as Internet access or read-access to the user"s contacts.
  • Declares the minimum required by the app, based on which APIs the app uses.
  • Declares hardware and software features used or required by the app, such as a camera, bluetooth services, or a multitouch screen.
  • Declares API libraries the app needs to be linked against (other than the Android framework APIs), such as the Google Maps library .

Declaring components

The primary task of the manifest is to inform the system about the app"s components. For example, a manifest file can declare an activity as follows:

...

For more about how to structure the manifest file for your app, see documentation.

Declaring component capabilities

Declaring app requirements

There are a variety of devices powered by Android and not all of them provide the same features and capabilities. To prevent your app from being installed on devices that lack features needed by your app, it"s important that you clearly define a profile for the types of devices your app supports by declaring device and software requirements in your manifest file. Most of these declarations are informational only and the system does not read them, but external services such as Google Play do read them in order to provide filtering for users when they search for apps from their device.

For example, if your app requires a camera and uses APIs introduced in Android 2.1 ( 7), you must declare these as requirements in your manifest file as shown in the following example:

...

With the declarations shown in the example, devices that do not have a camera or have an Android version lower than 2.1 cannot install your app from Google Play. However, you can declare that your app uses the camera, but does not require it. In that case, your app must set the attribute to false and check at runtime whether the device has a camera and disable any camera features as appropriate.

More information about how you can manage your app"s compatibility with different devices is provided in the document.

App resources

An Android app is composed of more than just code—it requires resources that are separate from the source code, such as images, audio files, and anything relating to the visual presentation of the app. For example, you can define animations, menus, styles, colors, and the layout of activity user interfaces with XML files. Using app resources makes it easy to update various characteristics of your app without modifying code. Providing sets of alternative resources enables you to optimize your app for a variety of device configurations, such as different languages and screen sizes.

For every resource that you include in your Android project, the SDK build tools define a unique integer ID, which you can use to reference the resource from your app code or from other resources defined in XML. For example, if your app contains an image file named logo.png (saved in the res/drawable/ directory), the SDK tools generate a resource ID named R.drawable.logo . This ID maps to an app-specific integer, which you can use to reference the image and insert it in your user interface.

One of the most important aspects of providing resources separate from your source code is the ability to provide alternative resources for different device configurations. For example, by defining UI strings in XML, you can translate the strings into other languages and save those strings in separate files. Then Android applies the appropriate language strings to your UI based on a language qualifier that you append to the resource directory"s name (such as res/values-fr/ for French string values) and the user"s language setting.

Android supports many different qualifiers for your alternative resources. The qualifier is a short string that you include in the name of your resource directories in order to define the device configuration for which those resources should be used. For example, you should create different layouts for your activities, depending on the device"s screen orientation and size. When the device screen is in portrait orientation (tall), you might want a layout with buttons to be vertical, but when the screen is in landscape orientation (wide), the buttons could be aligned horizontally. To change the layout depending on the orientation, you can define two different layouts and apply the appropriate qualifier to each layout"s directory name. Then, the system automatically applies the appropriate layout depending on the current device orientation.

How Android works on different types of devices and an introduction to how you can optimize your app for each device or restrict your app"s availability to different devices. How Android restricts app access to certain APIs with a permission system that requires the user"s consent for your app to use those APIs.