Статьи

Учебник по удаленному обслуживанию Android: AIDL — как структурировать проект и lib

В этом посте я опишу, как использовать Remote Service в Android. Этот вид сервисов — это сервисы, которые могут использоваться другими процессами, использующими RPC (удаленный вызов процедур). В другом посте мы говорим о локальном сервисе, другими словами, приложение, которое размещает сервис, может использовать его. Сервисы AIDL полезны, когда мы хотим создать новые функции и распространять их в виде библиотеки. Интересный аспект, который мы должны учитывать при разработке службы AIDL, заключается в том, что она может вызываться / использоваться компонентами, работающими в разных процессах. Чтобы поддерживать IPC (межпроцессное взаимодействие), в Android мы должны определить интерфейс, который описывает методы, которые будут доступны клиенту. Для создания этого интерфейса мы используем AIDL (язык определения интерфейса Android).

Учитывая, что эти удаленные сервисы могут распространяться в виде библиотеки, мы должны выбрать, что мы хотим дать клиенту (в виде jar), чтобы он мог вызывать и использовать наш сервис. Таким образом, важно иметь правильные структуры проекта, чтобы мы могли создать клиентский jar, содержащий только необходимые классы. В оставшейся части этого поста мы сосредоточим наше внимание и на этом аспекте.
В качестве примера мы будем использовать тот же пример, который был описан в прошлый раз, когда мы получаем котировки акций.

Определите удаленную службу AIDL

Для создания Сервиса AIDL у нас есть:

  1. определить и создать интерфейс службы, используя AIDL
  2. Реализуйте наш сервис и переопределите метод onBind, возвращая наш интерфейс
  3. Определите объекты, которыми обмениваются клиент и сервер, и деконструируйте их на низком уровне ОС, чтобы их можно было маршалировать и не маршалировать. Другими словами, наши классы должны реализовывать интерфейс Parcelable .
  4. Настройте наш сервис в файле Manifest.xml

В нашем примере мы знаем, что просто хотим узнать котировку акций, поэтому для простоты наш интерфейс создается только одним методом getQuote. В этом методе мы передаем класс Stock pojo, который содержит информацию о биржевом коде и значениях, которые будут заполнены нашим сервисом в ответ. Наш класс pojo называется Stock. Итак, учитывая все сказанное, мы имеем, что AIDL это:

1
2
3
4
5
6
7
package com.survivingwithandroid.aidlservicetutorial.service;
 
import com.survivingwithandroid.aidlservicetutorial.service.Stock;
 
interface IStockService{
    void getQuote(Stock stock);
}

обратите внимание, что в строке 3 мы просто импортируем определение Stock, а в строке 6 мы определяем наш метод. С другой стороны, мы должны определить в AIDL наше Stock pojo:

1
2
3
package com.survivingwithandroid.aidlservicetutorial.service;
 
parcelable Stock;

Таким образом, мы определили интерфейс нашего сервиса. Если вы используете Eclipse, вы можете поместить эти два файла в поле source и имя пакета. Eclipse создаст все необходимое для использования сервиса.

Внедрить удаленную службу AIDL

Теперь у нас есть наш интерфейс, поэтому мы можем реализовать «настоящий» сервис Android:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public class StockService extends Service {
    ...
  
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("Srv", "OnBind");
        final ResultReceiver rec = (ResultReceiver) intent.getParcelableExtra("rec");
          
        return new IStockService.Stub() {
            @Override
            public void getQuote(Stock stock) throws RemoteException {
                (new Thread(new Worker(stock, rec))).start();
            }
        };
    }
  
  
}

Как всегда, чтобы создать сервис в Android, мы расширяем класс Service, предоставляемый SDK (строка 1). Самое важное, что нужно сделать, когда мы хотим реализовать удаленный сервис, это переопределить метод onBind и вернуть реализацию нашего интерфейса (строка 7). В строке 9 мы реализуем интерфейсный метод getQuote, вызывающий поток для получения котировки акций. Обратите внимание, что здесь мы использовали метод ResultReceiver, чтобы уведомить клиента о результате .

Клиентская реализация

Последний шаг — реализация клиента, который вызывает и использует сервис. Для разработки клиента нам необходимо:

  1. Сервисный интерфейс (описан в AIDL)
  2. Класс pojo, которым обмениваются клиент и сервер

С помощью этих двух элементов мы можем создать нашего клиента. Позже мы увидим, как структурировать проект.

Когда мы используем удаленный сервис, мы должны «привязать» нашего клиента к удаленному сервису. Мы можем сделать это, например, в методе onCreate нашей Activity, используя метод bindService:

1
2
3
Intent i = new Intent(IStockService.class.getName());
...
bindService(i, serviceConnection, Context.BIND_AUTO_CREATE);

где serviceConnection — это прослушиватель, мы используем некоторые методы обратного вызова, которые можно использовать для мониторинга состояния подключения службы. Поэтому мы должны создать экземпляр ServiceConnection для обработки:

  1. Событие подключения к услуге
  2. Событие отключения службы
01
02
03
04
05
06
07
08
09
10
11
12
13
14
ServiceConnection serviceConnection = new ServiceConnection() {
     @Override
     public void onServiceConnected(ComponentName name, IBinder binder) {
         Log.d("Srv", "Service connected!");
         service = IStockService.Stub.asInterface(binder);
         Log.d("Srv", "Service interface ["+service+"]");
     }
 
     @Override
     public void onServiceDisconnected(ComponentName name) {
         Log.d("Srv", "Service disconnected!");
         service = null;
     }
 };

В строке 5 мы наконец получаем наш сервисный интерфейс, который можно использовать для вызова удаленных методов на сервисной стороне. Когда у нас есть интерфейс, мы можем вызвать его, как если бы метод находился в одном из наших классов.

Структура проекта

Один важный аспект, который мы должны учитывать при разработке удаленного сервиса, — это то, что клиент должен называть нашим сервисом. Очень простой подход — смешать классы pojo, aidl и реализацию сервиса только в одной библиотеке и распространить ее среди разработчиков, которые хотят использовать наш сервис. Даже если этот подход работает, у него есть некоторые недостатки:

  1. Баночка может иметь большой размер, и разработчики клиентских приложений должны включить его в свой дистрибутив приложения.
  2. мы распространяем в качестве jar реализацию нашего сервиса, и, возможно, было бы разумнее держать его в другом месте.
  3. Даже если мы не изменяем интерфейс сервиса и pojos, а только реализацию сервиса, мы не выравниваем jar клиента и сервера

На мой взгляд, гораздо шире правильно структурировать проект, чтобы мы могли распределять между разработчиками клиента только тот класс, который им действительно нужен. Если мы используем Eclipse, мы можем создать два разных проекта, один для сервера (реализация нашего сервиса) и один для lib на стороне клиента. Мы должны помнить одну важную вещь: пометить этот последний проект как библиотеку.

android_aidl_service_project_structu [7]

Теперь в проекте AIDLServiceLib мы добавляем все, что нужно клиенту:

  1. Определение AIDL
  2. Pojos ссылки в AIDL

android_aidl_service_project_structu [2]

в то время как в AIDLServiceTutorial у нас есть:

android_aidl_service_project_structu [6]

Теперь нужно просто сделать ссылку между этими двумя проектами, другими словами, AIDLServiceTutorial использует AIDLServiceLib в качестве библиотеки:

android_aidl_service_project_structu

Теперь, если мы хотим создать клиента, мы просто создаем другой проект:

android_aidl_service_project_client2 [2]

Таким образом, мы отделили клиентские классы lib от реализации службы и можем распространять jar, связанный с AIDLServiceLib. Последнее, что нужно запомнить, — это установить AIDLServiceLib как lib для AIDLServiceClient.

  • Исходный код скоро будет доступен!