Разработка под Android / [Из песочницы] AIDL (Android Interface Definition Language) и коммуникация между процессами (IPC)

в 10:11, , рубрики: android, IPC, метки: ,

В данной статье мы попытаемся описать свой опыт работы с AIDL в Android IPC.
В ней содержится пример приложения с сервисом, который запущен в отдельном процессе.

Статью стоит рассматривать как:

  • пример архитектуры приложения, использующего remote Android Services и AIDL.
  • полезные примеры кода.
  • исключительно как дополнение к основной документации на Android Developers (см. ссылки в конце статьи).

Базовые понятия

Service – это компонентAndroid приложения без интерфейса пользователя, предназначенныйдля осуществления ресурсоемких и/или длительных операций.

Типы Android сервисов

  • Started (запущенные) — сервисы которые запускаются любым другим компонентом приложения (Activity, BrodcastReceiver, Service) и работают пока не остановят сами себя или кто-то не остановит их.
  • Bound (связанные) — сервис который выступает в роли сервера в клиент-серверной архитектуре. Такой сервис создается при первом соединении(запросе) от другого компонента приложения.Сервис останавливается, когда отсоединится последний клиент.
  • Сервис может быть одновременно и Started и Bound. Такой сервис способен «жить вечно» и обслуживать запросы клиентов.

Сервис может быть запущен в отдельном от Activity процессе.

Преимущества:

  • максимальный размер памяти увеличивается в 2 раза, например 32 МБ/процесс (зависит от платформы).
  • GC ведет себя менее агрессивно, если у вас есть 2 процесса со snapshot в N МБкаждый, чем 1 процесс и 2*N МБ.
  • стандартные преимущества Android сервисов: background, независимость от Activity, прочее.

Недостатки:

  • дополнительные ресурсы системы на низкоуровневую сериализацию и десериализацию.
  • необходимость контроля жизненного цикла процесса.
  • немного больше кода.

AIDL

В буквальном переводе – язык описания интерфейсов Android. Используется для описания композиции и декомпозиции Java объектов в примитивы ОС для непосредственно передачи между процесами.
AIDL файлы очень похожи на стандартные интерфейсы в java за исключением:

  • Импортировать нужно даже те aidl файлы, которые находятся в том же пакете.
  • Ключевое слово onewayв декларации void метода означает что метод будет вызван асинхронно (клиент не дожидается его выполнения).
  • Использовать можно только примитивы, String, List и Parcelable классы, объявленные в других aidl файлах.

С помощью AIDL автоматически генерируется java код для генерации stab’ов.

Архитектура приложения

Разработанное нами приложение — галерея для Android которая позволяет просматривать фотографии из карты памяти и сетей обмена фотографиями.
Основными задачами сервиса в данном приложении являются: получение метаданных (информации о альбомах, фотографиях, друзьях), мониторинг их обновлений и всего остального, что с ними связано. Сервис постоянно хранит актуальную информацию и готов в любой момент отдать ее основной Activity для отображения.
Ниже будут приведены ключевые участки кода и описан процесс создания примитивного сервиса:
Для осуществления общения между сервисом и Activity используются следующие AIDL файлы:

IDataSourceService.aidl – интерфейс сервиса:
packagecom.umobisoft.habr.aidlexample.common;
import com.umobisoft.habr.aidlexample.common.IDataSourceServiceListener;
interfaceIDataSourceService{
voidloadAlbums(in IDataSourceServiceListener listener);

}

IDataSourceServiceListener.aidl – интерфейс слушателей сообщений от сервиса:
package com.umobisoft.habr.aidlexample.common;
import com.umobisoft.habr.aidlexample.common.pojo.Album;

interface IDataSourceServiceListener{
oneway void albumItemLoaded(in Album a);
}

Данные передаются с помощью двух классов, которые реализуют интерфейс Parcelable — Album и Photo. Декларация aidl файлов для этих классов обязательна. При конвертации из примитивов ОС в java Объекты используется класс Creator.
Для записи данных используется метод writeToParcel интерфейса Parcelable:
@Override
public void writeToParcel(Parcel out, int flags) {
try{
out.writeLong(id);
out.writeString(name);
out.writeTypedList(photos);
}catch (Exception e) {
Log.e(TAG, "writeToParcel", e);
}
}

Также существует вспомогательный метод describeContents, его задача описать специальные случаи/состояния объектакоторые когут использоватся при сериализации и десириализации:

@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}

Методы чтения данных оказались недостойными вынесения их в состав интерфейса Parcelable, но стандартная практика – использование Creator вместе с:

private void readFromParcel(Parcel in) {
try{
id = in.readLong();
name = in.readString();

photos.clear();
in.readTypedList(photos, Photo.CREATOR);
}catch (Exception e) {
Log.e(TAG, "readFromParcel", e);
}
}

Activity запускает сервис (делая его таким образом StartedService) в методе onCreate

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textView = (TextView)findViewById(R.id.album_text);
Intent serviceIntent = newIntent(this, DataSourceService.class);
startService(serviceIntent);
connectToService();
}

На этом же этапе жизненного цикла она соединяется с сервисом:

private void connectToService() {
Intent intent = newIntent(this, DataSourceService.class);
this.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}

Процесс соединения с сервисом асинхронный, в нем учувствует реализация интерфейса ServiceConnection. Во время соединения с сервером Activity регистрируется в сервисе как слушатель сообщений, с помощью имплементации IDataSourceServiceListener.Stub:

private ServiceConnection serviceConnection = newServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "Service connection established");

serviceApi = IDataSourceService.Stub.asInterface(service);

try {
mainListener = newIDataSourceServiceListener.Stub() {
@Override
publicvoidalbumItemLoaded(final Album a) throwsRemoteException {
mToastHandler.post(new Thread(){
publicvoid run(){
Toast.makeText(HabrahabrAIDLExampleActivity.this, a.toString(), Toast.LENGTH_LONG).show();
textView.setText(a.toString());
}
});
}
};
serviceApi.loadAlbums(mainListener);
} catch (RemoteException e) {
Log.e(TAG, "loadAlbums", e);
}
}

@Override
publicvoidonServiceDisconnected(ComponentName name) {
Log.i(TAG, "Service connection closed");
serviceApi = null;
connectToService();
}
};

Во время работы StartedService, в случае если к-во свободной памяти уменьшатся до определенного порога, система может убить сервис без предупреждения. После этого система обязана перезапустить сервис. Таким образом, в методе onServiceDisconnected мы опять инициализируем связь с сервисом.

Надеюсь выложенные исходные тексты помогут разработчикам ознакомится с AIDL. Архив с полным примером тут.
Основное приложение доступно для ознакомления в Android Market.

Официальная документация на AndroidDevelopers:
Services: developer.android.com/guide/topics/fundamentals/services.html
AIDL: developer.android.com/guide/developing/tools/aidl.html

Автор: umobisoft

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js