👉 AIDL или Android Interface Definition Language, это язык, который позволяет нам описывать интерфейсы межпроцессного общения. Язык чем-то похож на урезанную java с некоторыми интересными ключевыми словами.
🙌 Суть в чем, описываем в интерфейсе те функции какие мы хотим вызывать на другом процессе, а также входные аргументы и тип результата. Пример того, как может выглядеть этот интерфейс:
// ICalculator.aidl
interface ICalculator {
int sum(int first, int second);
}
В простых примерах синтаксис вообще не отличается от java (кстати заметили ублюдский🤬 префикс "I" будто мы в C#, в AIDL по-прежнему используется этот пережиток прошлого). После того как мы написали интерфейс, компилятор генерирует классы Proxy и Stub.
Proxy – то, что использует клиент, чтобы вызывать функции другого процесса так, будто они находятся в одном процессе.
Stub – использует сервис, или по другому сервис теперь должен реализовать методы, которые мы определили в интерфейсе.
Проще будет понять на примере, сервис будет выглядеть так:
override fun onBind(intent: Intent?): IBinder {
return object : ICalculator.Stub() {
override fun sum(first: Int, second: Int): Int {
return first + second
}
}
}
На стороне клиента мы должны теперь получить Proxy, делаем мы это через байндинг этого сервиса и уже сгенеренных методов ICalculator:
val intent = Intent(this, CalculatorMessengerService::class.java)
bindService(intent, object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) = Unit
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val proxy = ICalculator.Stub.asInterface(service)
val sum = proxy.sum(1, 4)
}
}, BIND_AUTO_CREATE)
На этом все, теперь можно напряму вызывать методы другого процесса.
Proxy делает "маршалинг" (Marshalling), т.е перегоняет аргументы функций в последовательность байт. Stub соответственно делает "анмаршалинг" (Unmarshaling), перегоняет поток байт обратно в объекты, которые описаны в AIDL.
🤓 Еще раз пройдемся по всем компонентам которые у нас есть, советую параллельно смотреть в картинку.
У каждого Binder есть Token, по сути это просто некоторый desriptor, который помогает системе понять что это конкретно за реализация Binder. Если заглянуть в метод sum, который сгенерирован в proxy, мы увидим что там первой строчкой при маршалинге будет:
_data.writeInterfaceToken(DESCRIPTOR);
Эта строчка означает, что когда мы передаем массив байт в Stub, первые байты будут нести информацию о самом Binder.
Интерфейс IBinder это интерфейс, который описывает методы нужные для межпроцессной коммуникации. Binder стандартная реализация интерфейса IBinder, в Binder реализованы все сложные методы коммуникации и который мы можем расширять, чтобы использовать. Stub наследует Binder и нам остается только реализовать те методы которые мы указали в AIDL. Proxy использует объект IBinder, просто передать данные в Stub.
И теперь представь 🌈, все Intent, ContentProvider, все сервисы которые мы получаем через getSystemService() - все это работает через Binder и AIDL, круто не правда ли?
🎉 Если ты дочитал до сюда и примерно сможешь это пересказать, то я тебя поздравляю ты охренеть какая умничка и теперь сможешь выпендится на собесе. Тема очень сложная и используется только на очень больших проектах которым мало одного процесса или в каких-нибудь SDK, как например Яндекс.Метрика.
В посте было очень много упрощений, чтобы не делать его супер большим, если тебе понравилась тема стоит посмотреть этот доклад, который расскажет все это, но подробнее.