Добавлен: 29.10.2018
Просмотров: 48052
Скачиваний: 190
896
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
а Parcel создается из данных транзакции и доставляется принадлежащим этому объекту
методом onTransact.
Теперь эти три класса существенно упрощают написание IPC-кода:
1. Нужно создать подкласс из Binder.
2. Нужно реализовать метод onTransact для декодирования и выполнения входящих
вызовов.
3. Нужно реализовать соответствующий код для создания Parcel, который может
быть передан методу transact этого объекта.
Основная часть работы приходится на два последних этапа. Здесь создается код демар-
шализации и маршализации, который нужен для превращения (с помощью простых
вызовов методов) того, что отправлено, в операции, необходимые для выполнения IPC.
Это скучный при написании и не застрахованный от ошибок код, поэтому нам хотелось
бы, чтобы за нас обо всем этом мог позаботиться компьютер.
Интерфейсы Binder и AIDL
Заключительной частью Binder IPC является то, что используется чаще всего, — мо-
дель программирования высокого уровня, основанная на интерфейсе. Здесь вместо
работы с объектами Binder и данными Parcel нужно размышлять в понятиях интер-
фейсов и методов.
Основная часть этого уровня представлена средством командной строки под названием
AIDL
(Android Interface Definition Language — язык определения Android-интерфейса).
Это средство является компилятором интерфейса, получающим абстрактное описание
интерфейса и создающим из него исходный код, необходимый для определения этого
интерфейса и реализации соответствующей маршализации и демаршализации кода,
необходимого для осуществления с его помощью удаленных вызовов.
В листинге 10.3 показан простой пример интерфейса, определенного в AIDL. Этот
интерфейс называется IExample и содержит единственный метод print, которому пере-
дается единственный аргумент String.
Листинг 10.3. Простой интерфейс с описанием на AIDL
package com.example
interface IExample {
void print(String msg);
}
Описание интерфейса, подобное приведенному в листинге 10.13, компилируется AIDL
для создания трех классов на языке Java (рис. 10.30):
1. IExample — класса, поставляющего определение интерфейса на языке Java;
2. IExample.Stub — базового класса для реализации этого интерфейса. Он наследу-
ется из класса Binder, следовательно, может быть получателем IPC-вызовов. Он
наследуется из IExample, поскольку это реализуемый интерфейс. Этот класс пред-
назначен для выполнения демаршализации: превращения поступающих вызовов
onTransact в соответствующий вызов метода IExample. Затем его подкласс отвечает
только за реализацию методов IExample;
10.8. Android
897
3. IExample.Proxy — класса, находящегося по другую сторону IPC-вызова и отве-
чающего за осуществление маршализации вызова. Он занимается конкретным
обеспечением выполнения IExample, вставляя каждый его метод для превращения
вызова в соответствующее содержимое Parcel и отправляя его через вызов transact
в адрес IBinder, с которым налажена связь.
Binder
IExample
IExample.Stub
IExample.Proxy
IBinder
Рис. 10.30. Иерархия наследования в интерфейсе Binder
При наличии этих классов больше не нужно волноваться за механику IPC. Реализа-
торы интерфейса IExample просто выводятся из IExample.Stub и реализуют методы
интерфейса, что от них обычно и требуется. Вызывающие стороны получат интерфейс
IExample, реализованный с помощью IExample.Proxy, что позволит им совершать обыч-
ные вызовы интерфейса.
Способ совместной работы этих частей для выполнения полной IPC-операции показан
на рис. 10.31. Простой вызов print в интерфейсе IExample превращается в следующие
действия:
1. IExample.Proxy осуществляет маршализацию вызова метода в Parcel, вызывая
transact в отношении основного BinderProxy.
2. BinderProxy выстраивает транзакцию для ядра и доставляет ее ядру через вызов
ioctl.
3. Ядро переправляет транзакцию намеченному процессу, доставляя ее потоку, ко-
торый ожидает свой собственный вызов ioctl.
4. Транзакция декодируется обратно в Parcel, и в отношении соответствующего
локального объекта, в данном случае ExampleImpl (являющегося подклассом
IExample.Stub), вызывается метод onTransact.
5. IExample.Stub декодирует Parcel в соответствующий метод и аргументы вызова,
вызывая в данном случае print.
6. И наконец, в ExampleImpl выполняется конкретная реализация print.
С использованием этого механизма в Android написана основная часть IPC. Боль-
шинство служб в Android определены через AIDL и реализованы так, как показано
здесь. Вспомним рис. 10.23, на котором показано, как реализация диспетчера пакетов
в процессе system_server использует IPC для собственной публикации с помощью
диспетчера служб, осуществляемой для других процессов, чтобы дать им возможность
отправлять к ней вызовы. Здесь задействованы два AIDL-интерфейса: для диспетчера
служб и для диспетчера пакетов. Например, в листинге 10.4 показано основное AIDL-
описание для диспетчера служб. В нем содержится метод getService, используемый
другими процессами для извлечения IBinder интерфейсов системных служб, подобных
диспетчеру пакетов.
898
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Процесс 1
Ядро
Examplelmpl
IExample
print("hello")
print("hello")
IExample.Proxy
IExample.Stub
transact({print hello})
onTransact({print hello})
Binder
BinderProxy
ioctl()
ioctl()
binder_module
Процесс 1
Рис. 10.31. Полный путь Binder IPC на основе AIDL
10.8.8. Приложения Android
Android предоставляет модель приложений, имеющую существенные отличия от обыч-
ной среды командной строки в оболочке Linux или даже от приложений, запускаемых
из графического интерфейса пользователя. Приложение не является исполняемым
файлом с основной точкой входа, оно представляет собой контейнер для всего, из чего
складывается приложение: его кода, графических ресурсов, объявлений о том, чем оно
является для системы, и других данных.
Листинг 10.4. AIDL-интерфейс основного диспетчера служб
package android.os
interface IServiceManager {
IBinder getService(String name);
void addService(String name, IBinder binder);
}
По соглашению приложение Android является файлом с расширением
apk
, что означает
Android Package
(пакет Android). Это обычный zip-архив, в котором содержится все,
касающееся приложения. В
apk
-файле имеется следующее важное содержимое:
1. Манифест, дающий описание того, чем является приложение, что оно делает и как
его запустить. Манифест должен предоставить пакетное имя для приложения,
строку, оформленную в Java-стиле (например,
com.android.app.calculator
), которая
идентифицирует его уникальным образом.
2. Ресурсы, необходимые приложению, включая строки, которые оно показывает
пользователю, XML-данные для компоновки и другие описания, графические
побитовые изображения и т. д.
3. Сам код, который может быть байт-кодом Dalvik, а также собственного библио-
течного кода.
4. Информация о подписи, надежно идентифицирующей автора.
10.8. Android
899
С учетом целей, которые здесь стоят перед нами, ключевой частью приложения явля-
ется манифест, который выглядит как предварительно скомпилированный XML-файл
по имени
AndroidManifest.xml
, находящийся в корневой части пространства имен zip-
архива, представляющего
apk
-файл. Полноценный пример объявления манифеста для
гипотетического приложения электронной почты показан в листинге 10.5. Это при-
ложение дает вам возможность просматривать и составлять сообщения электронной
почты и также включает компоненты, необходимые для синхронизации его локального
хранилища электронной почты с сервером, даже когда пользователь не находится
в приложении.
У Android-приложений нет простой основной точки входа в приложение, с которой
начинается выполнение кода при запуске приложения. Вместо этого они публикуют
под тегом манифеста <application> ряд точек входа, описывая при этом различные дей-
ствия, на которые способно приложение. Эти точки входа выражаются в виде четырех
различных типов, определяя основные типы поведения, предоставляемые приложени-
ем: активность (activity), получатель (receiver), служба (service) и поставщик контента
(content provider). Представленный пример показывает несколько активностей и одно
объявление из числа других типов компонентов, но приложение может декларировать
ноль и больше любых из этих типов компонентов.
Каждый из четырех типов компонентов приложения может содержать свою, отлич-
ную от других семантику и указание на порядок использования внутри системы. Во
всех случаях атрибут android:name представляет имя Java-класса в коде приложения,
реализующего этот компонент, экземпляр которого будет создан системой при необ-
ходимости.
Листинг 10.5. Основная структура файла AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.email">
<application>
<activity android:name="com.example.email.MailMainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.example.email.ComposeActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<service android:name="com.example.email.SyncService">
</service>
<receiver android:name="com.example.email.SyncControlReceiver">
<intent-filter>
900
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
<action android:name="android.intent.action.DEVICE STORAGE LOW" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DEVICE STORAGE OKAY" />
</intent-filter>
</receiver>
<provider android:name="com.example.email.EmailProvider"
android:author ities="com.example.email.provider.email">
</provider>
</application>
</manifest>
Диспетчер пакетов
(package manager) — часть системы Android, следящая за всеми
пакетами приложений. Он анализирует каждый манифест приложения, собирает
и индексирует найденную в нем информацию. Затем, используя эту информацию,
предоставляет клиентам удобную возможность запроса данных о текущих установ-
ленных приложениях и извлечения важной информации о них. Он также отвечает
за установленные приложения (создание пространства под хранилище приложения
и обеспечение целостности
apk
-файла), а также за все необходимое для их удаления
(очистку всего связанного с ранее установленным приложением).
Объявление приложениями своих точек входа в манифестах носит статический харак-
тер, поэтому они не нуждаются в выполнении кода, регистрирующего их в системе во
время установки. Такая конструкция делает систему надежнее во многих отношениях:
установка приложения не требует запуска какого-либо прикладного кода, присущие
приложению возможности верхнего уровня могут быть определены в любой момент
путем просмотра манифеста, не нужно вести отдельную базу данных с этой инфор-
мацией, которая может оказаться не синхронизированной (например, в результате
обновлений) с фактическими возможностями приложения, — и она гарантирует, что
после удаления приложения о нем не останется абсолютно никакой информации. Такой
децентрализованный подход был предпринят во избежание множества тех проблем,
которые создает централизованный реестр Windows.
Разбиение приложения на четко детализированные компоненты также служит целям
конструирования по поддержке взаимодействия и сотрудничества между приложе-
ниями. Приложения могут публиковать свои собственные части, предоставляющие
конкретные функции, которыми другие приложения могут воспользоваться как не-
посредственно, так и опосредованно. Мы проиллюстрируем это более подробно при
рассмотрении четырех типов компонентов, которые могут быть опубликованы.
Если диспетчер пакетов отвечает за обслуживание статической информации обо всех
установленных приложениях, то диспетчер активностей определяет, когда, где и как
эти приложения должны запускаться. Несмотря на его название, он фактически отве-
чает за работу всех четырех компонентов приложения и реализует соответствующее
поведение для каждого из них.
Активности
Активность
(activity) является частью приложения, взаимодействующей непосред-
ственно с пользователем через пользовательский интерфейс. Когда пользователь
запускает приложение на своем устройстве, в приложении в качестве основной точки