3 марта 2014 г.

Архитектура Android-приложений. Часть IV — интеграционный уровень

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

Как было показано ранее, фреймворк Android реализует несколько шаблонов взаимодействия:


Обмен сообщениями

Очевидно, это первый механизм взаимодействия, с которым сталкивается разработчик, начав программировать под Android. Он используется для запуска активити или сервиса. Мехнанизм реализуется с помощью трёх методов класса Context (о котором говорилось в одной из предыдущих статей) и одного метода класса Activity:

  • startActivity() класса Context запускает активити
  • startActivityForResult() класса Activity запускает активити
  • startService() или bindService() класса Context запускает сервис, если это необходимо, и связывается с ним (между этими двумя методамми существует некоторая разница)

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

Как Android узнаёт, какому активити или сервису адресуется сообщение? Для этого есть два способа:

  • Intent может явно указывать имя компонента. Такие интенты называются явными («explicitIntents»). Целевой компонент определяется методами setComponent() и setClass(). Этот тип интент-ориентированного механизма взаимодействия не реализует шаблон позднего связывания, в отличие от всех остальных.
  • Целевой компонент определяется весьма необычным механизмом принятия решения, основанном на фильтрах интентов.

Имейте ввиду, что в случае отправки сообщения (экземпляра класса Intent) механизм взаимодействия доставит его только одному компоненту, или не доставит вовсе.

Издатель/подписчик

Механизм взаимодействия издатель/подписчик использует тот же класс Intent в качестве сообщения и те же фильтры, что и механизм определения целевого компонента, но работает несколько иначе. Интенты, используемые в данном случае, называются «широковещательными». Эти интенты могут быть доставлены только BroadcastReceiver'у.

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

Фактически, Android реализует два разных механизма издатель/подписчик, каждый из которых имеет по две версии (обычную и «липкую», «sticky»):

  • Нормальное вещание реализуется методами sendBroadcast() и sendStickyBroadcast() класса Context.
  • Упорядоченное вещание реализуется методами sendOrderedBroadcast() и sendStickyOrderedBroadcast().

Я же ещё не сбил вас с толку, так?

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

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

  • Использовать результаты работы предыдущего броадкаст-ресивера, который работал над тем же интентом
  • Если необходимо, прервать дальнейшую обработку интента

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

Упорядоченное вещание является реализацией шаблона цепочка обязанностей.

«Липкое» вещание — разновидность обычного вещания. Разница в том, что интент доступен до тех пор, пока он не будет явно удалён методом removeStickyBroadcast() класса контекст. Помните, что этот тип вещания следует использовать с осторожностью, поскольку не удалённые «липкие» интенты приводят, в итоге, к утечке памяти.

Выводы по интент-ориентированному механизму взаимодействия

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

Отсюда можно сделать вывод, что все механизмы взаимодействия Android используют класс Intent. Но это не совсем так.

Позднее связывание ContentProvider'ов

В большинстве случаев класс ContentProvider используется как обёртка вокруг базы данных SQLite. Хотя, теоретически, он может быть использован для других механизмов хранения\получения данных. Об использовании этого класса можно почитать здесь.

Существует множество способов использования контент-порвайдеров. Одни включают получение объектов, которые работают как прокси над контент-порвайдером (обычно это экземпляр или подкласс ContentProviderClient, который реализует шаблон прокси). Другие возвращают экземпляр класса Cursor, который позволяет перебирать множество данных, возвращённых конетнт-провайдером. В любом случае, конкретный ContentProvider должен идентифицироваться по URI (подробнее см. тут).

В любом случае, это типичный шаблон позднее связывание.

Позднее связывание и межпроцессное взаимодействие (IPC)

Если вы думаете, что на этом механизмы взаимодействия закончились, то вы ошибаетесь.

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

  • Локальный сервис запускается в том же процессе, где и вызывается. Архитектурно он похож на контент-провайдер, за исключением того, что у первого имеется собственный жизненный цикл и отсутствует обязательная оглядка на БД. Для доступа к локальному сервису используется разновидность позднего связывания.
  • Удалённый сервис работает в другом процессе, а не в том, из которого он был вызван. Связь с таким сервисом происходит через разновидность IPC, называемую AIDL (Android Interface Definition Language).

Немного сбивает с толку то, что один и тот же сервис может быть использован и как локальный, и как удалённый; разница в том, где расположены компоненты, взывающие сервис (в том же процессе или в другом).

Комментариев нет:

Отправить комментарий