Перейти к содержанию

Binder IPC и AIDL

Аббревиатуры и основные термины

Аббревиатура Полное название Значение в этой статье
IPC Interprocess Communication Межпроцессное взаимодействие: передача данных и вызов операций между процессами
AIDL Android Interface Definition Language Язык описания типизированных Binder-интерфейсов
UID User Identifier Идентификатор пользователя Linux, используемый в изоляции Android-приложений
PID Process Identifier Идентификатор работающего процесса; ненадежен для авторизации
API Application Programming Interface Программный интерфейс доступа к функциональности
SDK Software Development Kit Инструменты разработки и поддерживаемые публичные Android API
UI User Interface Пользовательский интерфейс; его главный поток должен оставаться отзывчивым
I/O Input/Output Ввод-вывод: например, чтение файлов или работа с сетью
URI Uniform Resource Identifier Идентификатор ресурса, например данных, предоставляемых провайдером
OS Operating System Операционная система, в данном случае Android
AOSP Android Open Source Project Открытый исходный код платформы Android и документация
APEX Android Pony EXpress Формат контейнера для независимо обновляемых низкоуровневых системных модулей
HAL Hardware Abstraction Layer Слой интерфейсов между Android framework и реализациями для конкретного оборудования
ANR Application Not Responding Ситуация, когда приложение не отвечает в установленные системой сроки

Binder - название, а не аббревиатура. Оно обозначает механизм IPC в Android и связанные интерфейсы. Parcel - контейнер данных транзакции; Parcelable - контракт записи значения в него и восстановления из него. Proxy представляет удаленный интерфейс на клиенте; Stub принимает и диспетчеризует вызовы на сервере.

Binder - основной механизм межпроцессного взаимодействия (IPC) в Android. AIDL добавляет поверх Binder типизированный контракт и сгенерированный код. Код приложения чаще сталкивается с ними косвенно через менеджеры Android framework, но понимание этой границы помогает правильно работать с связанные службы, потоками, ошибками и безопасностью.

Зачем Android нужен IPC

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

IPC используется, когда приложение обращается к компоненту в другом процессе приложения, а также когда публичный framework API взаимодействует с платформенной службой. Компонент приложения Service и платформенная или системная служба - разные понятия. Кроме того, Service не получает отдельный процесс или фоновый поток автоматически: по умолчанию он работает в процессе приложения, а его методы жизненного цикла выполняются в главном потоке этого процесса.

Модель Binder

В Binder-взаимодействии участвуют несколько основных частей:

  • клиентский процесс вызывает клиентский proxy;
  • proxy сериализует идентификатор метода и поддерживаемые значения в Parcel;
  • Binder driver в ядре переносит транзакцию через границу процессов;
  • Binder thread в серверном процессе принимает транзакцию;
  • серверный Binder-объект или stub десериализует запрос и вызывает реализацию;
  • для двустороннего вызова результат или исключение возвращается в reply Parcel.

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

Многие Android framework API используют Binder внутри. Например, публичный manager может быть оболочкой приложения над удаленной платформенной службой. Обычному приложению следует работать через публичный SDK API, а не искать скрытые Binder services напрямую. Android использует ядро Linux; Binder поддерживает сервисную архитектуру платформы, но не превращает Android в ОС с микроядром.

Что добавляет AIDL

Android Interface Definition Language (AIDL) описывает типизированный интерфейс, согласованный между процессами. Инструменты сборки генерируют Binder-обвязку, включая серверный Stub и клиентский Proxy. Сервер расширяет сгенерированный stub или делегирует ему работу, а клиент получает интерфейс из IBinder.

Аргументы и результаты должны допускать передачу через границу процессов. Обратно передаются только поддерживаемые типы исключений; произвольное исключение приложения не является контрактом удаленной ошибки. AIDL поддерживает примитивы, некоторые платформенные типы, другие AIDL-интерфейсы и объявленные parcelables. Для пользовательского типа требуется общее определение, которое обе стороны сериализуют одинаково. Например:

// DeviceStatus.aidl
package com.example.status;

parcelable DeviceStatus;
// IDeviceStatusService.aidl
package com.example.status;

import com.example.status.DeviceStatus;

interface IDeviceStatusService {
    DeviceStatus getStatus();
}

Здесь DeviceStatus должен быть поддерживаемым AIDL типом Parcelable в том же пакете, а клиент и сервер должны использовать его совместимое определение. Пользовательский тип требует явного импорта в AIDL даже в том же пакете. Для параметров типа parcelable нужно указать in, out или inout; предпочтителен in, если серверу не требуется записывать значения обратно. AIDL не заменяет Binder и не является другим транспортом. Он генерирует типизированный Binder-интерфейс и берет на себя значительную часть шаблонного кода сериализации и диспетчеризации.

Связь с bound service

Приложение может предоставить Binder endpoint через bound Service. Сервер возвращает IBinder из Service.onBind(). После bindService() клиент асинхронно получает binder в ServiceConnection.onServiceConnected() и преобразует его через IDeviceStatusService.Stub.asInterface(service). Метод возвращает локальную реализацию, если она доступна, либо proxy для удаленных вызовов. При потере или освобождении соединения ссылку на интерфейс нужно сбросить.

Следует выбирать самый простой механизм, который соответствует границе процессов и требованиям к конкурентности:

Вариант Когда подходит
Custom Binder Клиент и service работают в одном процессе
Messenger Межпроцессные message-based запросы с последовательной обработкой
AIDL Типизированный межпроцессный контракт, возможно с несколькими конкурентными клиентами

Для распространенного варианта в одном процессе custom Binder проще. Messenger передает сообщения через Handler, поэтому удобен, когда запросы должны обрабатываться последовательно. Прямой AIDL оправдан, если действительно нужен стабильный типизированный API между процессами и реализация готова к конкурентным вызовам. Для обычного application service это не вариант по умолчанию.

Потоки, время жизни и ошибки

Удаленные AIDL-методы по умолчанию синхронные: клиент ждет ответа сервера. Потенциально медленный удаленный вызов нельзя выполнять в главном потоке. Даже простой на вид метод может заблокироваться, если сервер занят, выполняет I/O или ожидает свободный Binder thread. Объем передаваемых данных также должен быть небольшим: Binder transactions используют ограниченные буферы и не предназначены для передачи крупных файлов, bitmap или графов объектов. Документированный размер буфера сейчас составляет около одного мегабайта и делится между всеми выполняющимися транзакциями процесса. Это не гарантированный лимит на каждый вызов. Большой запрос или ответ может вызвать TransactionTooLargeException. Для больших данных лучше передать файловый дескриптор, URI с необходимыми правами доступа или идентификатор.

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

Модификатор AIDL oneway отправляет удаленный запрос без ожидания его выполнения и ответа. Такой метод возвращает void и не может иметь параметры out или inout. Локальный вызов остается синхронным даже с oneway. Отправка не подтверждает завершение операции; управление нагрузкой и обработку сбоев нужно проектировать явно.

Удаленный процесс может завершиться в любой момент. Вызов способен выбросить RemoteException, в том числе DeadObjectException, если удаленный endpoint больше недоступен. В onServiceDisconnected() нужно сбросить сохраненный интерфейс; binding остается активным и может восстановиться автоматически. В onBindingDied() нужно выполнить unbind и повторный bind, если соединение еще требуется. onNullBinding() означает, что соединение не станет доступным; его также нужно освободить. IBinder.DeathRecipient - более низкоуровневый механизм наблюдения за смертью Binder endpoint для сложных интеграций. При любом варианте IPC вызов bindService() нужно сопоставить с unbindService() согласно жизненному циклу владельца соединения.

Граница безопасности

Экспортируемый bound service создает IPC entry point, к которому может попытаться обратиться другое приложение. Для подключения к известному service лучше использовать explicit Intent, а приватный service объявлять с android:exported="false". Если соединение с другими приложениями необходимо, чувствительные операции нужно защищать подходящими permissions и проверять право каждого caller на запрошенное действие.

Используйте Binder.getCallingUid() во время входящей транзакции для проверки прав клиента. Получайте и проверяйте идентичность до передачи работы в другой поток: вне транзакции этот API возвращает UID собственного процесса. Binder.getCallingPid() нужен преимущественно для диагностики: PID может переиспользоваться и недоступен для oneway, поэтому на нем нельзя строить авторизацию. При необходимости следует учитывать shared UID, проверять package или подпись через поддерживаемые API и не доверять переданным клиентом полям с его идентичностью. Binder безопасно переносит запрос через границу процессов, но не делает экспортируемый контракт безопасным автоматически.

AIDL приложений и платформенный AIDL

Разработчик приложения может использовать AIDL напрямую между собственными процессами или взаимодействующими приложениями. Это app-level модель из документации Android Developers. Отдельно Android framework services используют множество внутренних AIDL-контрактов за публичными managers и SDK API. Доступ к скрытому ServiceManager и внутренним AOSP interfaces - детали реализации, а не поддерживаемый публичный API для обычных приложений.

Stable AIDL добавляет контроль совместимости и версионирование для платформенных компонентов, которые могут обновляться независимо, включая некоторые границы APEX и HAL. Он решает задачу платформенной интеграции и вводит более строгие правила интерфейса. Обычному application-level AIDL service, как правило, не нужен build workflow Stable AIDL, но его контракт все равно должен сохранять совместимость с независимо обновляемыми клиентами.

См. также

Дополнительные материалы