Статьи

Android AIDL и удаленный клиент


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

В  предыдущей статье мы описали, как настроить удаленный сервис с использованием AIDL. Перед этим в  первой статье мы продемонстрировали реализацию  пользовательского  класса Parcelable User для передачи между процессами. Теперь мы рассмотрим клиента и, наконец, представим небольшой конкретный пример по телефону. Разговоры о концепциях — это хорошо, но, как инженеры, нам нужно какое-то работающее программное обеспечение. В  конце этой статьи будет загружен ZIP-файл AIDL  Client / Service , чтобы читатель мог поиграть с кодом.

Поскольку нам нужно проверить доступ к сервису  из другого процесса , мы начнем с создания отдельного проекта Android из нашего сервиса. Нам понадобится очень простой пользовательский интерфейс, чтобы мы могли протестировать наш удаленный сервис. Поэтому наш клиент должен быть  Деятельностью . Наш клиент также должен иметь возможность:

  1. Подключиться к сервису, т.е. связать его с помощью  ServiceConnection
  2. Получите необходимые данные, используя сгенерированную заглушку сервиса

Наш основной клиент будет иметь следующий скелет:

package com.ts.dataclient;

import android.app.Activity;
import android.content.ServiceConnection;
// etc...
// import the Service aidl files
import com.ts.userdata.User;
import com.ts.userdata.IUserDataService;

/**
 * Client screen to bind to UserDataService
 * */
public class DataClientActivity extends Activity {

/** Service to which this client will bind */
IUserDataService dataService;
/** Connection to the service (inner class) */
DataConnection  conn;

       // etc...

}

И мы реализуем наш  ServiceConnection  как внутренний класс клиентского  Activity :

 /** Inner class used to connect to UserDataService */
    class DataConnection implements ServiceConnection { 

    /** is called once the bind succeeds */
        public void onServiceConnected(ComponentName name, IBinder service) {
        dataService = IUserDataService.Stub.asInterface(service);
        }

        /*** is called once the remote service is no longer available */
        public void onServiceDisconnected(ComponentName name) { //
        dataService = null;
        }

      }

Метод  Stub.asInterface  был одним из автоматически генерируемых методов инструментом AIDL, когда мы настраивали наш сервис. Остальное в значительной степени самодокументировано.

Далее мы реализуем несколько   методов жизненного цикла Activity для клиента:

/** Called when the activity is first created */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // connect to the service
        conn = new DataConnection();
        // name must match the service's Intent filter in the Service Manifest file
        Intent intent = new Intent("com.ts.userdata.IUserDataService");
        // bind to the Service, create it if it's not already there
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /** Clean up before Activity is destroyed */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
        dataService = null;
    }

Далее мы создадим очень простой пользовательский интерфейс, состоящий из поля и кнопки отправки. Вот наиболее важные части:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ...>
   ...
   <EditText android:id="@+id/uid" ../>
   <Button android:text="Submit" android:onClick="getData" ../>
</linearLayout>

Затем нам нужно получить фактические данные, которые мы хотим от удаленного сервиса. При отправке (  обработчик onClick ) мы отображаем сообщение Toast с данными:

 /** Handler for Submit button */
    public void getData(View v){

    User usr = null; // User is Parcelable @see first article
    try {
    // get user input
    EditText userid = (EditText) findViewById(R.id.uid);
            long id = Long.parseLong( userid.getText().toString().trim() );
    // call the AIDL service
     usr = dataService.lookUpUser(id); 

     }
     catch(NumberFormatException nfex){
        Toast.makeText(this, "Id must be a number.", Toast.LENGTH_SHORT).show();
     }
     catch (RemoteException e) {
            Toast.makeText(this, "Service unavailable", Toast.LENGTH_SHORT).show();
     }

     if(usr != null){
        Toast.makeText(this, usr.toString(), Toast.LENGTH_LONG).show();
 }
    }

Мы почти закончили. Возвращаясь к нашему сервису в  предыдущей статье , мы добавим несколько строк кода, чтобы вернуть что-то конкретное клиентам, чтобы мы могли проверить, все ли это работает:

package com.ts.userdata;

import com.ts.userdata.IUserDataService;

// other imports here

/** Laughably simple service implementation. We'll return some data if user enters id=101.
  * For all other values, we return a non-registered, empty set */
public class UserDataService extends Service {

@Override
public IBinder onBind(Intent arg0) {
return new IUserDataService.Stub() { // generated by the System
          public User lookUpUser(long id) throws RemoteException {
         // TEST
          User usr = new User();
          usr.setId(101L);
          usr.setAge(30);
          usr.setPhone("123 456 7890");
          usr.setRegistered(true);  

          // Anonymous user
          User anonym = new User();
          anonym.setPhone("Not Published");
          anonym.setRegistered(false);

          return ( id == usr.getId() ? usr : anonym );
          }
    };
}
}

Вышеуказанная реализация, конечно, скрыта для клиентов и не распространяется никому за пределами службы. Однако клиенту необходим доступ к файлам AIDL, а также к   классу Parcelable User (подробнее об этом позже), чтобы иметь возможность использовать службу. Поэтому нам нужно скопировать их  с оригинальной структурой пакета  в клиентский проект. В демонстрационных целях мы одновременно развернем оба приложения на телефоне, просто развернув клиент. Самый простой способ сделать это — включить приложение-службу в путь сборки клиентского приложения (в Eclipse перейдите в Project / Properties / Java Build Path / и добавьте проект Service). Как только мы это сделаем и развернем, вот что мы получаем на реальном телефоне:

МПК сработал. Мы эффективно общались между процессами, то есть приложениями для Android.

Обратите внимание, что мы должны распространять не только файлы AIDL, но и наш  пользовательский  класс Parcelable User среди наших клиентов, поскольку они должны получить его на другом конце канала IPC. Это может быть проблемой, потому что  пользователь не является интерфейсом. Если нам когда-нибудь потребуется изменить  его  реализацию, мы  сломаем наших существующих клиентов. Более простой и стабильный сервис AIDL бы один , который использует только типы , предоставляемые системой, которые поддерживаются вне коробки, как наш  телефон службы поиска  в предыдущей статье, возвращающего  список  из  Струнные  типов. Таким образом, последнее слово, держать эти вопросы в виду при принятии решения о создании собственных  Parcelable  классов в AIDL.

AIDL клиент / сервис  .zip файл:  Android-AIDL.zip