Android AIDL
AIDL – язык определения интерфейсов в Android. С помощью языка определения интерфейсов мы определяем программный интерфейс, благодаря которому клиент и Android сервис могут взаимодействовать друг с другом. И необходимо понимать, что AIDL нужен только если необходимо другим приложениям обеспечить доступ к нашему сервису или если необходима многопоточность в нашем приложении(VPN приложение отличный кандидат на многопоточность в сервисе).
Алгоритм предельно прост:
- Создаем AIDL файл
- реализуем интерфейс
- Предоставляем реализацию клиентам
AIDL интерфейс поддерживает следующие типы данных:
- Java примитивы(char,boolean,short и т.д.)
- Массивы Java примитивов(int[], double[] и т.д.)
- String
- CharSequence
- List(элементы List должны принадлежать к одному из поддерживаемых типов)
- Map(элементы Map должны принадлежать к одному из поддерживаемых типов). Если требуется более широкий функционал, то можно рассмотреть вариант использования Bundle в качестве альтернативы.
Также необходимо помнить про следующие пункты:
- методы могут принимать ноль и более параметров и могут как возвращать, так и нет данные(для примеры мы определим далее тип возвращаемого значения void и List<String>)
- Если мы используем непримитивные данные(объекты) – нам необходимо указывать в каком направлении идут данные с помощью ключевых слов in, out или inout. По умолчанию примитивы, String и IBinder имеет тип направления in
- Все комментарии кода, включенные в AIDL файл переносятся в сгенерированный IBinder интерфейс. Кроме комментариев перед импортом пакетов.
- Допускается определение констант типа String и int. Например const String PACKAGE = “it-nptepad.com”;
- Типы аргументов и возвращаемых значений, которые могут быть null, необходимо пометить аннотацией @nullable
Приступим:
Для примера представим реализацию сервиса, которая будет предоставлять клиентам список доступных видео камер(пример реальный из жизни). Теперь создадим новый проект в Android Studio. Создадим наш сервис – GetVideoList. Наша задача – предоставить возможность передачи List<String> как один из самых распространенных вариантов.
Теперь создадим наш AIDL файл. Необходимо помнить, что файлы AIDL необходимо размещать в отдельной папке – aidl. См. скринштот. Мы назовем его – RemoteVideoService.aidl.
Теперь Мы выполним сборку нашего проекта – Ctrl + 9. Смотрим папку java(generated) – теперь там появился наш пакет, определенный в AIDL файле -online.salattime.videoapp. После сборки у нас сгенерировался наш интерфейс, только уже в виде java файла – RemoteVideoService.java. Полный код ниже. Бояться его не стоит – у нас сгенерировалось автоматически много вспомогательных методов.
/* * This file is auto-generated. DO NOT MODIFY. */ package online.salattime.videoapp; public interface RemoteVideoService extends android.os.IInterface { /** Default implementation for RemoteVideoService. */ public static class Default implements online.salattime.videoapp.RemoteVideoService { /** * Get's actually List of Id's camera videos. */ @Override public java.util.List<java.lang.String> getListVideos(int requestId, long transaction, java.lang.String hash) throws android.os.RemoteException { return null; } @Override public void exampleVoid(int id) throws android.os.RemoteException { } @Override public android.os.IBinder asBinder() { return null; } } /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements online.salattime.videoapp.RemoteVideoService ... /** * Get's actually List of Id's camera videos. */ public java.util.List<java.lang.String> getListVideos(int requestId, long transaction, java.lang.String hash) throws android.os.RemoteException; public void exampleVoid(int id) throws android.os.RemoteException; }
Мы не включили более 100 строк кода, в связи с их абсолютной ненадобностью. Все интересующее нас привидено выше.
Теперь осталось реализовать наш интерфейс из сгенерированного AIDL файла:
private final RemoteVideoService.Stub binder = new RemoteVideoService.Stub() { @Override public List<java.lang.String> getListVideos(int requestId, long transaction, String hash) throws RemoteException { return null; } @Override public void exampleVoid(int id) throws RemoteException { } };
На этом все. Шутка) Мы же саму реализацию интерфейса для клиентов не сделали. Тут тоже особых сложностей не возникнет.
public class GetVideoList extends Service { @Override public void onCreate() { super.onCreate(); } @Nullable @Override public IBinder onBind(Intent intent) { return binder; } final RemoteVideoService.Stub binder = new RemoteVideoService.Stub() { @Override public List<java.lang.String> getListVideos(int requestId, long transaction, String hash) throws RemoteException { return null; } @Override public void exampleVoid(int id) throws RemoteException { } }; }
Теперь покажем, как необходимо подключаться к нашему сервису… Выполняем bindService() и получаем экзамерляр нашего интерфейса.
RemoteVideoService remoteService; private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established public void onServiceConnected(ComponentName className, IBinder service) { // Following the example above for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service remoteService = RemoteVideoService.Stub.asInterface(service); } // Called when the connection with the service disconnects unexpectedly public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "Service has unexpectedly disconnected"); remoteService = null; } }
На этом все. Однако я хочу задержаться на одном моменте, причем очень важном:
Передача объектов через IPC интерфейс
Что такое IPC? Точнее IPC интерфейс? IPC – это межпроцессное взаимодействие. Не путать с межпроцессорным. Начиная с Android 10, можно напрямую определять Parcelable объекты в AIDL файлах. Это позволяет нам избавиться от лишнего кода и лишней работы. Давайте попробуем создать наш Parcelable объект в AIDL файле. Назовем его VideoParcelable и определим в нем произвольные поля типа String, int, long, double.
// VideoParcelable.aidl package online.salattime.videoapp; parcelable VideoParcelable { String videoLink; int id; long timeCreated; double duration; }
В папке gen после сборки проекта мы увидим сгенерированный автоматически файл VideoParcelable.java. Перед попыткой извлечения нашего объекта, не забудем вызвать Bundle.setClassLoader(ClassLoader), иначе получим исключение ClassNotFoundException. Например
private final VideoService.Stub binder = new VideoService.Stub() { public void saveVideoObject(Bundle bundle){ bundle.setClassLoader(getClass().getClassLoader()); VideoParcelable videoObject = bundle.getParcelable("videoObject"); } };
В заключении опишем алгоритм по вызову IPC службы
- Создать все необходимые
.aidl
файлы вsrc/aidl
каталоге проекта . - Объявляем экземпляр интерфейса
IBinder
(созданный на основе AIDL файла). - Реализация
ServiceConnection
. - Вызов
Context.bindService()
, для связи с удаленным сервисом. - В реализации
onServiceConnected()
получаем экземплярIBinder
(называемый сервисом). Далее приводим тип к типу нашего интерфейса: YourInterface .
YourInterfaceName
service.Stub.asInterface((IBinder)
)
- Вызываем удаленные сервисы. Не забываем перехватывать исключения
DeadObjectException
, которые возникают при разрыве соединения. Мы также должны перехватывать исключенияSecurityException
которые возникают, когда два процесса, участвующие в вызове метода IPC, имеют конфликт в определении AIDL интерфейса. - Чтобы отсоеденииться от удаленного сервиса, вызываем
Context.unbindService()
.