Реалізація міжпроцесорної взаємодії (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 — це зекономить ваш час і бюджет.







