пятница, 5 августа 2011 г.

Основы программирования на платформе Android. Часть 2. Архитектура Android. Цикл жизни приложения

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

Архитектура операционной системы Android

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


Уровень аппаратного обеспечения мы в данном случае обойдём стороной и не будем рассматривать, так как это к делу не относится. Для нас интерес будут представлять уровни, находящиеся выше.

Уровень ядра Linux

В основе операционной системы Android лежит ядро Linux  версии 2.6. Однако стоит заметить, что данное ядро переработано и не является полной копией родительского ядра. На данном уровне происходит управление процессами, распределением памяти и файловой системой. Также в состав ядра входят компоненты для управления энергопотреблением и драйверы устройств на мобильном телефоне. Ядро играет роль слоя абстракции между аппаратным обеспечением и остальным слоем программного обеспечения.

Уровень библиотек и среды исполнения

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

  • OpenGL ES – движок для работы с 3D-графикой на базе OpenGL ES 2.0. Является упрощённой версией OpenGL;
  • SQLite - процессор для работы с базой данных. Важной особенностью является то, что данная СУБД не реализует клиент-серверную архитектуру. Вместо этого движок SQLite компонуется вместе с приложением, то есть фактически становится частью приложения. Именно по причине того, что данная СУБД является встраиваемой, работа с ней осуществляется с довольно высокой скоростью. Сама база данных хранится в одном файле, который существует в одном экземпляре на компьютере, на котором исполняется программа;
  • WebKit - библиотека, предназначенная для работы встроенного в Android Web-браузера;
  • FreeType  – библиотека шрифтов;
  • Media Framework  – библиотека для работы с различными форматами файлов мультимедиа.
На этом же уровне архитектуры Android присутствует ещё одна очень важная и интересная составляющая. Это среда выполнения, представленная виртуальной машиной Dalvik и библиотеками ядра, которые предоставляют функциональность для Java. Dalvik Virtual Machine является одной из реализаций Java-машин, но надо признать что по многим фундаментальным аспектам от неё отличается. Самое главное отличие в том, что Java-машина компании Oracle является стековой, в то время, как Dalvik Virtual Machine является регистровой. Такая реализация была выбрана для увеличения производительности на RISC-архитектурах. Выполняется на данной виртуальной машине не стандартный байт-код Java, а байт-код Dalvik, который имеет формат .dex. Имеется возможность транслировать байт-код формата .class в байт-код формата .dex с помощью утилиты dx, входящую в состав SDK. Также хочется отметить, что, начиная с Android 2.2, Dalvik VM поддерживает JIT-компиляцию, что также положительно сказывается на производительности. Каждый процесс обладает собственной копией Dalvik VM, то есть изнутри Android можно себе представить как множество работающих виртуальных машин Dalvik, на которых выполняются процессы. Этим обеспечивается изолированность процессов друг от друга.

Уровень каркаса приложений

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

Уровень приложений

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

Жизненный цикл приложения

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

Начнём с того, что разберёмся с таким важным понятием, как приложение. В любой операционной системой под приложением понимают программу, написанную на некотором языке программирования, которая хранится на диске в виде одного или нескольких файлов и выполняет определённую задачу. На платформе Android приложения обычно пишутся на языке Java, далее запаковываются в файл формата .apk и распространяются на устройства, на которых они смогут выполнять поставленную задачу. Каждое приложение обычно работает в рамках одного процесса. Процессом называется программа во время выполнения, а также ресурсы, необходимые её для работы. На каждый процесс в Android выделен отдельный экземпляр Dalvik VM, как уже писалось выше.

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

Итак, всего существует 4 типа компонентов:
  • Activity  (Деятельность);
  • Service  (Служба);
  • Broadcast Receiver  (Приёмники широковещательных сообщений);
  • Content Provider (Поставщики содержимого).
Приведённые переводы я буду по возможности избегать.

Данные компоненты как раз имеют вполне определённое время жизни. Рассмотрим их по-отдельности. В дальнейшем основное наше внимание при проектировании системы мы будем уделять компоненту Activity, другие использоваться не будут в дальнейшей работе, но знать о них нужно при программировании на Android.

Activity – один из важнейших компонентов приложения Android. Является средством непосредственного взаимодействия программы с пользователем. В общем случае Activity – это инструмент, позволяющий пользователю работать с конкретно поставленной задачей. Допустим, у нас имеется приложение для отправки SMS, в таком случае можно для данного приложения определить следующий набор Activity:
  1. Ввод сообщения;
  2. Поля, для ввода номеров контактов, которым будет отправлена SMS;
  3. Список выбора нужных контактов для отправки сообщения.
Этот список можно дополнить, например, выделить Activity для выбора языка. Суть заключается в том, что каждая Activity отвечает за свою задачу, и дальше передаёт эстафету следующему компоненту (не обязательно Activity).

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

Перед тем, как Activity станет видна пользователю вызывается метод onStart(). Перед тем, как начать взаимодействовать с пользователем вызывается метод onResume(), если же приложение становится фоновым, то вызывается метод onStop(). После метода onResume() всегда рано или поздно выполнится метод onPause(). Произойдёт это, когда другая Activity попытается выйти на передний план. Все Activity располагаются в специальном стеке, который так и называется стеком Activity. В данном случае другая Activity попытается занять вершину этого самого стека. В методе onPause() обычно сохраняются все нужные данные и останавливаются анимация и другие вещи, которые загружают центральный процессор. После метода onPause() вызывается либо метод onResume(), либо метод onStop(). Первый вызывается в том случае, если деятельность возвращается на вершину стека Activity. Второй метод вызывается в том случае, если Activity стала невидима пользователю. Далее вызывается либо метод onRestart(), который перезапускает Activity, либо метод onDestroy(), который её уничтожает. После вызова метода onDestroy() жизненный цикл Activity завершается.

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

Broadcast receiver – важный компонент приложения. Сам никогда ничего не делает, но всё знает и слышит, а потому может заставить других сделать нужные вещи за него. Похож на диспетчера из службы заказа пиццы: после звонка диспетчер скорее всего сам не встаёт и не везёт пиццу, он лишь выбирает одного из свободных разносчиков и отдаёт ему поручение с необходимой информацией, о том куда и сколько доставить. Broadcast receiver способен принять сообщение, а потом просто передать их дальше обработчику, чтобы тот мог уже превратить сообщение в нужную информацию и подать сигнал пользователю, если понадобится. Сигналом может быть вибрация устройства, специфический звук, некоторое сообщение (например о том, что заряд батареи на низком уровне) и т. п. То есть в задачу Broadcast receiver входит приём сообщения и реакция на него, которая заключается в передаче необходимых данных другому обработчику, такому как Activity. Также не имеет собственного пользовательского интерфейса.

Content provider – компоненты, служащие для того, чтобы сделать некоторый набор данных доступным для других приложений. Эти данные могут находиться в файловой системе или в базе данных SQLite или хранится другим способом. Контроль за доступом к данным осуществляется посредством разрешений прав доступа. Можно сконфигурировать собственный Content provider и установить необходимые разрешение на нужные наборы данных. Аналогично предыдущим двум типам компонентов не имеет GUI.

Хочется отметить одну важную вещь. Android является довольно гибкой системой в плане работы приложений. Несмотря на то, что по умолчанию каждое приложение запускается в своём процессе, имеется возможность запускать каждый компонент приложения в своём процессе. Это размывает привычные «границы» приложений. Нетронутыми лишь остаются «границы» процесса и потока. Потоки используются в основном, чтобы запустить некоторую задачу, например скачивание данных с сервера, при этом сохранив для пользователя возможность работать с приложением.

И ещё одно замечание. Если Вы обратите внимание на левую ветку схемы с изображением жизненного цикла Activity, то можете заметить там блок «Process id killed». Это происходит в том случае, если другому приложению нужна память, а освободить её можно, лишь завершив процесс выполнения. Хочется заметить, что завершается не Activity, а именно процесс, в котором данная Activity была запущена. Завершить процесс Android сможет только после вызова метода onPause(), то есть имеется возможность сохранить все данные, при этом методы onStop() и onDestroy() могут быть не вызваны.

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