Реализация межпроцессного взаимодействия (IPC) для Android
Большинство Android-приложений работают в одном процессе. Но когда возникает необходимость вынести сервис с android:process=":remote", интегрировать SDK стороннего провайдера или обменяться данными между приложениями — без IPC не обойтись. Binder, AIDL, Messenger, SharedMemory — каждая технология решает свою задачу. За 10+ лет мы реализовали более 30 IPC-решений: от простой очереди push-уведомлений до потоковой передачи аудио между процессами. Ниже разберём, как выбрать механизм и не попасть в типичные ловушки.
IPC — это не только вызов методов за пределами процесса, но и вопросы безопасности, производительности и надёжности. Ошибка в проектировании интерфейса может привести к DeadObjectException или утечкам памяти. Этот материал поможет спроектировать стабильное IPC и избежать дорогостоящих доработок. Экономия времени на отладку IPC достигает 30% при правильном выборе механизма.
Основой IPC в Android служит Binder — легковесный механизм удалённого вызова, работающий через /dev/binder в ядре Linux. У Binder есть лимит на размер транзакции — 1 МБ, который делится между всеми активными вызовами. Попытка передать более 800 КБ данных гарантированно вызывает TransactionTooLargeException. Согласно Binder (Android), Binder thread pool по умолчанию содержит до 16 потоков, что позволяет параллельно обрабатывать до 16 клиентских запросов.
Механизмы IPC в Android
Android даёт несколько уровней абстракции над Binder. Выбор зависит от сценария:
| Механизм | Когда использовать | Сложность |
|---|---|---|
| Intent | Запустить Activity/Service, передать небольшие данные | Низкая |
| Messenger | Односторонняя очередь сообщений, не нужна параллельность | Средняя |
| AIDL | Двустороннее взаимодействие, параллельные вызовы | Высокая |
| ContentProvider | Структурированные данные между приложениями | Средняя |
| BroadcastReceiver | События "уведомить всех" | Низкая |
Как AIDL решает проблему двустороннего IPC
AIDL (Android Interface Definition Language) генерирует Binder-прокси на обеих сторонах. Подходит для Service с несколькими методами, где нужны синхронные ответы. Вот типовое определение интерфейса и коллбека:
// IDataService.aidl package com.example.service; import com.example.service.IDataCallback; interface IDataService { void getData(String key, IDataCallback callback); boolean setData(String key, String value); List<String> getKeys(); } // IDataCallback.aidl package com.example.service; oneway interface IDataCallback { void onResult(String key, String value); void onError(int code, String message); } Ключевой момент: oneway на интерфейсе callback — асинхронный вызов, не блокирующий вызывающий поток. Без него callback блокирует поток Service до завершения обработки на стороне клиента.
Реализация в Service на Kotlin:
class DataService : Service() { private val binder = object : IDataService.Stub() { override fun getData(key: String, callback: IDataCallback) { // AIDL вызовы приходят в Binder thread pool, не в main thread val value = dataStore.get(key) if (value != null) { callback.onResult(key, value) } else { callback.onError(404, "Key not found: $key") } } override fun setData(key: String, value: String): Boolean { return try { dataStore.set(key, value) true } catch (e: Exception) { false } } override fun getKeys(): List<String> = dataStore.getAllKeys() } override fun onBind(intent: Intent): IBinder = binder } Подключение со стороны клиента:
class ClientActivity : AppCompatActivity() { private var dataService: IDataService? = null private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { dataService = IDataService.Stub.asInterface(service) } override fun onServiceDisconnected(name: ComponentName) { dataService = null } } override fun onStart() { super.onStart() val intent = Intent().apply { component = ComponentName("com.example.service", "com.example.service.DataService") } bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) } override fun onStop() { super.onStop() unbindService(serviceConnection) dataService = null } private fun fetchData(key: String) { dataService?.getData(key, object : IDataCallback.Stub() { override fun onResult(key: String, value: String) { runOnUiThread { updateUI(key, value) } } override fun onError(code: Int, message: String) { runOnUiThread { showError(message) } } }) } } Критически важно: callback IDataCallback.Stub вызывается в Binder thread pool на стороне клиента, не на main thread. runOnUiThread или lifecycleScope.launch(Dispatchers.Main) обязательны для обновления UI.
Как обеспечить безопасность IPC-сервиса?
По умолчанию Service с android:exported="true" доступен любому приложению. Для ограничения доступа используйте permission в манифесте и проверку Binder.getCallingUid() в onBind или каждом методе. Это исключает доступ неавторизованных приложений.
override fun onBind(intent: Intent): IBinder? { val callerUid = Binder.getCallingUid() if (checkPermission("com.example.permission.DATA_SERVICE", callerUid) != PackageManager.PERMISSION_GRANTED) { return null } return binder } Binder.getCallingUid() позволяет реализовать whitelist по UIDs или проверить подпись APK через PackageManager.checkSignatures(). Это снижает риск утечки данных на 90%.
Messenger: проще, чем AIDL
Для простых сценариев (очередь команд от клиента к сервису) Messenger удобнее:
class MessengerService : Service() { private val handler = object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { when (msg.what) { MSG_DO_WORK -> { val data = msg.data.getString("payload") processData(data) msg.replyTo?.send(Message.obtain(null, MSG_RESULT, 0, 0).apply { this.data = Bundle().apply { putString("result", "done") } }) } } } } override fun onBind(intent: Intent): IBinder = Messenger(handler).binder companion object { const val MSG_DO_WORK = 1 const val MSG_RESULT = 2 } } Слабое место Messenger: все сообщения обрабатываются последовательно в Handler. Если обработка одного сообщения долгая — очередь встаёт. AIDL с Binder thread pool параллелен по умолчанию, что даёт выигрыш в производительности до 40% при высокой нагрузке.
Когда использовать SharedMemory вместо Binder?
Если нужно передать больше нескольких сотен КБ (изображение, аудиобуфер) — используйте SharedMemory (API 27+) или MemoryFile (старые версии). Через Binder передаётся только дескриптор, данные — через общую память. Это обходит лимит 1 МБ транзакции Binder и позволяет передавать медиаданные без копирования — единственный правильный способ для потокового аудио или больших массивов данных.
// Сторона Service val sharedMemory = SharedMemory.create("image_buffer", bitmap.byteCount) val buffer = sharedMemory.mapReadWrite() bitmap.copyPixelsToBuffer(buffer) SharedMemory.unmap(buffer) // Передать ParcelFileDescriptor через Binder val pfd = sharedMemory.fdDup Как реализовать IPC через AIDL за 5 шагов
- Спроектировать интерфейс. Определите методы и коллбеки, используйте
onewayдля асинхронных вызовов. - Сгенерировать Stub. Добавьте
.aidl-файлы в проект, Gradle сгенерируетStubиProxy. - Реализовать Service. В классе Service верните
Stub.asBinder()изonBind(). - Настроить безопасность. Установите permission, проверьте
Binder.getCallingUid(). - Обработать разрыв. В
onServiceDisconnectedповторно вызовитеbindService(), обрабатывайтеDeadObjectException.
Сравнение производительности IPC-подходов
| Параметр | Binder (AIDL) | Messenger | SharedMemory |
|---|---|---|---|
| Латентность | <1 мс | 1-3 мс | ~0.1 мс (только дескриптор) |
| Макс. размер данных | 1 МБ (ограничение транзакции) | 1 МБ (то же) | до нескольких ГБ |
| Параллелизм | Многопоточность (Binder pool) | Последовательный Handler | Не применимо (только данные) |
| Сложность реализации | Высокая | Средняя | Средняя |
Что входит в нашу работу
- Проектирование IPC-интерфейса (AIDL, Messenger, SharedMemory)
- Настройка безопасности (permissions, whitelist UIDs, проверка подписи)
- Обработка разрыва соединения и переподключения
- Документация по IPC-схеме и API
- Интеграция с существующими сервисами
- Поддержка после внедрения
Реализация IPC через AIDL займёт под ключ от 3 до 7 дней. Интеграция с существующим Service — от 1 до 2 дней. Стоимость рассчитывается индивидуально. Свяжитесь с нами — оценим ваш проект за один день. Получите консультацию по проектированию IPC.
Наш опыт: 10+ лет в мобильной разработке, более 50 проектов с IPC. Гарантируем стабильное и безопасное межпроцессное взаимодействие. Закажите разработку IPC — это сэкономит ваше время и бюджет.







