Bookmark and Share
You are here: Home Laboratorio Google Android Documentazione Materiale Progettare e usare un'interfaccia remota usando AIDL
Document Actions

Progettare e usare un'interfaccia remota usando AIDL

by Alice Narduzzo last modified 2008-04-03 09:33

Dato che ogni applicazione gira sul suo proprio processo, e che puoi scrivere un servizio che giri in un processo differente dall'UI della tua applicazione, a volte hai bisogno di passare oggetti tra diversi processi. Nella piattaforma Android, un processo normalmente non può accedere alla memoria di un altro processo. Quindi per comunicare, devono decomporre i loro oggetti in primitivi che il sistema operativo possa capire, e "mettere in ordine logico" gli oggetti al di là di quel limite per te.

Il codice per eseguire quella specie di ordinamento è noioso e prolisso da scrivere, per questo forniamo lo strumento AIDL, che lo farà al posto vostro.

AIDL (Android Interface Definition Language) è un linguaggio IDL usato per generare codice che permetta a due processi su un dispositivo Android di comunicare usando interprocess communication (IPC). Se hai del codice in un processo (per esempio, in un'activity) che ha bisogno di richiamare metodi su un oggetto in un altro processo (per esempio, un servizio), potresti usare AIDL per generare codice per coordinare i parametri.

Il meccanismo AIDL IPC è interface-based, simile a COM o Corba, ma di peso minore. Usa una classe proxy per passare valori tra il client e l'implementazione.

Questa pagina include i seguenti argomenti:

Implementare IPC usando AIDL

Segui queste tre fasi per implementare un servizio IPC usando AIDL.

  1. Creare il tuo file .aidl - Questo file definisce un'interfaccia (YourInterface.aidl) che a sua volta definisce i metodi e campi disponibile per un client. 
  2. Aggiungi il file .aidl al tuo makefile - (il plugin Eclipse gestisce tutto ciò per te). Android include il compilatore (compiler), chiamato AIDL, nella directory tools/.
  3. Implementare i tuoi metodi di interfaccia - Il compilatore AIDL crea un'interfaccia nel linguaggio di programmazione Java dalla tua interfaccia AIDL. Questa interfaccia ha una classe interna astratta chiamata Stub che eredita l'interfaccia (e implementa un paio di metodi addizionali necessari per la chiamata IPC). Devi creare una classe che estenda YourInterface.Stub e implementi i metodi  che ha dichiarato nel tuo file .aidl.
  4. Esporre la tua interfaccia ai clients - Se stai scrivendo un servizio, dovresti estendere Service e riscrivere Service.onBind(Intent) per restituire un'istanza della tua classe che implementa l'interfaccia.

Creare un file .aidl

AIDL è una sintassi semplice che ti permette di dichiarare un'interfaccia con uno o più metodi, che può prendere parametri e restituire valori. Questi parametri e valori di ritorno possono essere di ogni tipo, anche altre interfacce generate con AIDL. Tuttavia, è importante prestare attenzione al fatto che devi importare tutti i tipi non-built-in, anche se sono definiti nello stesso pacchetto della tua interfaccia. Questi sono i tipi di dati (data types) che AIDL può supportare:

  • Tipi primitivi di linguaggio di programmazione JAVA (int, boolean, ecc) — Non è necessaria nessuna dichiarazione import.
  • Una delle seguenti classi (non è necessaria nessuna dichiarazione import):
    • String
    • List - Tutti gli elementi nella List devono essere di uno dei tipi in questa lista, incluse altre interfacce generate con AIDL e parcelables. List può essere usata alternativamente come una classe "generica"(per esempio, List<String>). L'attuale classe concreta che l'altra parte riceverà sarà sempre una ArrayList, anche se il metodo verrà generato in modo che usi l'interfaccia List.
    • Map - Tutti gli elementi in Map devono essere di uno dei tipi in questa lista, incluse altre interfacce generate con AIDL e parcelables. Maps generiche, (per esempio della forma Map<String,Integer>) non sono supportate. L'attuale classe concreta che l'altra parte riceverà sarà sempre una HashMap, anche se il metodo verrà generato in modo che usi l'interfaccia Map.
    • CharSequence - Questo è utile per i tipi CharSequence usati da TextView e altri oggetti widget.
  • Altre interfacce generate con AIDL, che sono sempre passate per riferimento. Una dichiarazione import è sempre necessaria per queste classi.
  • Classi personalizzate che implementano il Parcelable protocol e sono passate per valore. Una dichiarazione import è sempre necessaria per queste classi.

Questa è la sintassi AIDL di base:

// My AIDL file, named SomeClass.aidl
// Note that standard comment syntax is respected.
// Comments before the import or package statements are not bubbled up
// to the generated interface, but comments above interface/method/field
// declarations are added to the generated interface.

// Include your fully-qualified package statement.
package com.google.android.sample;

// See the list above for which classes need
// import statements (hint--most of them)
import com.google.android.sample.IAtmService;

// Declare the interface.
interface IBankAccountService {

// Methods can take 0 or more parameters, and
// return a value or void.
int getAccountBalance();
void setOwnerNames(in List<String> names);

// Methods can even take other AIDL-defined parameters.
BankAccount createAccount(in String name, int startingDeposit, in IAtmService atmService);

// All non-Java primitive parameters (e.g., int, bool, etc) require
// a directional tag indicating which way the data will go. Available
// values are in, out, inout. (Primitives are in by default, and cannot be otherwise).
// Limit the direction to what is truly needed, because marshalling parameters
// is expensive.
int getCustomerList(in String branch, out String[] customerList);
}

Implementare l'interfaccia

AIDL genera un file di interfaccia per te con lo stesso nome del file .aidl. Se stai usando un plugin Eclipse, AIDL verrà automaticamente eseguito come come parte del processo centrale (non hai bisogno di eseguire prima AIDL e poi costruire il tuo progetto). Se invece non usi il plugin, dovresti prima eseguire AIDL.

L'interfaccia generata include una classe interna astratta chiamata Stub che dichiara tutti i metodi che hai dichiarato nel tuo file .aidl. Stub definisce anche un paio di metodi di aiuto, (helper methods), specialmente asInterface(), che prende un IBinder (passato a un'implementazione onServiceConnected() del client quando applicationContext.bindService() va a buon fine), e restituisce un'istanza  dell'interfaccia usata per richiamare i metodi IPC. Vedi la sezione Richiamare un metodo IPC per ulteriori dettagli per come realizzare questo cast.

Per implementare la tua interfaccia, estendi YourInterface.Stub, e implementa i metodi. (puoi creare il file .aidl e implementare i metodi di stub senza fare del building in mezzo -- Il build process di Android processerà i files .aidl prima dei files .java)

Questo è un esempio di implementazione di un'interfaccia chiamata IRemoteService, che espone un singolo metodo, getPid(), usando un'istanza anonima:

// No need to import IRemoteService if it's in the same project.
private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
public int getPid(){
return Process.myPid();
}
}

Un paio di regole sull'implementazione della tua interfaccia:

  • Nessuna eccezione lanciata verrà inviata indietro al caller.
  • Le chiamate IPC sono sincrone. Se sai che un servizio IPC impiega più di un paio di millisecondi per completarsi, non dovresti richiamarlo nel thread Activity/View, perché potrebbe uccidere l'applicazione (Android potrebbe mostrare un pannello con il messaggio "Application is Not Responding"). Prova con un thread separato.
  • Solo i metodi sono supportati; non puoi dichiarare campi statici (static fields) in un'interfaccia AIDL.

Esporre la tua interfaccia ai clients

Ora che hai la tua implementazione di interfaccia, devi esporla ai clients. Quest'operazione è detta "publishing your service" (pubblicare il tuo servizio). Per pubblicare un servizio, eredita Service e implementa Service.onBind(Intent) per restituire un'istanza della tua classe che implementa l'interfaccia. Questo è un frammento di codice di un servizio che espone l'interfaccia IRemoteService ai clients.

public class RemoteService extends Service {
...
@Override
public IBinder onBind(Intent intent) {
// Select the interface to return. If your service only implements
// a single interface, you can just return it here without checking
// the Intent.
if (IRemoteService.class.getName().equals(intent.getAction())) {
return mBinder;
}
if (ISecondary.class.getName().equals(intent.getAction())) {
return mSecondaryBinder;
}
return null;
}

/**
* The IRemoteInterface is defined through IDL
*/
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public void registerCallback(IRemoteServiceCallback cb) {
if (cb != null) mCallbacks.register(cb);
}
public void unregisterCallback(IRemoteServiceCallback cb) {
if (cb != null) mCallbacks.unregister(cb);
}
};

/**
* A secondary interface to the service.
*/
private final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() {
public int getPid() {
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
}
};

}

Parametri pass by value (passati per valore) usando Parcelables

Attenzione: Parcelables non funzionano se userai il plugin Eclipse. Vedrai comparire questo avviso errore se provi ugualmente:
   .aidl files that only declare parcelables don't need to go in the makefile
   aidl can only generate code for interfaces, not parcelables
Questa è una limitazione conosciuta. Parcelables possono comunque essere usati con i files ant build.xml o con il tuo sistema personalizzato. Una soluzione è eseguire lo strumento aidl manualmente per ognuna delle tue istanze e aggiungerle al tuo progetto Eclipse. Vedi la fase 5 qui sotto per capire perché Eclipse non dovrebbe provare a compilare questi files aidl.

Se c'è una classe che vorresti inviare da un processo ad un altro attraverso un'interfaccia AIDL, puoi fare così. Devi assicurarti che il codice per la tua classe sia disponibile per l'altra parte del IPC. Generalmente, questo significa che stai comunicando con un servizio che hai avviato.

Ci sono cinque parti per fare in modo che una classe supporti il Parcelable protocol:

  1. Fare implementare alla tua classe l'interfaccia Parcelable.
  2. Implementare il metodo public void writeToParcel(Parcel out) che prende lo stato corrente dell'oggetto e lo scrive in un plico (parcel).
  3. Implementa il metodo public void readFromParcel(Parcel in) che legge il valore in un plico all'interno del tuo oggetto.
  4. Aggiungere un campo statico (static field) chiamato CREATOR alla tua classe che è un oggetto che implementa l'interfaccia Parcelable.Creator.
  5. Ultimo ma non meno importante, aggiungere un file aidl per la tua classe parcelable così che lo strumento AIDL possa trovarlo, ma non aggiungerlo al tuo build. Questo file è usato come un header file in C. Non compilerai il file aidl per un parcelable esattamente come normalmente non compileresti un file .h.

AIDL userà questi metodi e campi nel codice che genera per mobilitare e smobilitare i tuoi oggetti.

Questo è un esempio di come la classe Rect implementa il Parcelable protocol:

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
   
public int left;
   
public int top;
   
public int right;
   
public int bottom;

   
public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
       
public Rect createFromParcel(Parcel in) {
           
return new Rect(in);
       
}

       
public Rect[] newArray(int size) {
           
return new Rect[size];
       
}
   
};

   
public Rect() {
   
}

   
private Rect(Parcel in) {
        readFromParcel
(in);
   
}

   
public void writeToParcel(Parcel out) {
       
out.writeInt(left);
       
out.writeInt(top);
       
out.writeInt(right);
       
out.writeInt(bottom);
   
}

   
public void readFromParcel(Parcel in) {
        left
= in.readInt();
        top
= in.readInt();
        right
= in.readInt();
        bottom
= in.readInt();
   
}
}

Questo è Rect.aidl per questo esempio:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable
Rect;

Il coordinamento nella classe Rect è abbastanza semplice. Dai un'occhiata agli altri metodi in Parcel per vedere gli altri tipi di valori che puoi scrivere in un Parcel.

Attenzione: Non dimenticare le implicazioni riguardanti la sicurezza quando si tratta di ricevere dati da altri processi. In questo caso, il rect leggerà quattro numeri dal parcel, ma dipende date assicurarti che questi siano all'interno dell'intervallo accettabile di valori per qualsiasi tipo di operazione il caller stia cercando di svolgere. Vedi Sicurezza e permssi in Android per ulteriori dettagli su come mantenere la sicurezza della tua applicazione e difenderla dal malware.

Richiamare un metodo IPC

Queste sono le fasi che una calling class dovrebbe fare per richiamare la tua interfaccia remota:

  1. Dichiarare una variabile del tipo di interfaccia che il tuo file .aidl ha definito.
  2. Implementare ServiceConnection.
  3. Richiamare ApplicationContext.bindService(), passando per la tua implementazione ServiceConnection.
  4. Nella tua implementazione di ServiceConnection.onServiceConnected(), riceverai un'istanza IBinder (chiamata service). Richiama YourInterfaceName.Stub.asInterface((IBinder)service) mandare il parametro di ritorno al tipo YourInterface.
  5. Richiamare i metodi che hai definito nella tua interfaccia. Dovresti sempre bloccare eccezioni DeadObjectException, che sono lanciati quando la connessione si è interrotta; questa sarà l'unica eccezione lanciata da metodi remoti.
  6. Per disconnetterti, richiama ApplicationContext.unbindService() con l'istanza della tua interfaccia. 

Un paio di commenti sul richiamare un servizio IPC:

  • Gli oggetti sono riferimento conteggiato tra i processi.
  • Puoi inviare oggetti anonimi come argomenti di metodo.

Questo è del codice campione che dimostra il richiamare un servizio creato con AIDL, preso dal campione Remote Activity nel progetto ApiDemos.

public class RemoteServiceBinding extends Activity {
   
/** The primary interface we will be calling on the service. */
   
IRemoteService mService = null;
   
/** Another interface we use on the service. */
   
ISecondary mSecondaryService = null;

   
Button mKillButton;
   
TextView mCallbackText;

   
private boolean mIsBound;

   
/**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */

   
@Override
   
protected void onCreate(Bundle icicle) {
       
super.onCreate(icicle);

        setContentView
(R.layout.remote_service_binding);

       
// Watch for button clicks.
       
Button button = (Button)findViewById(R.id.bind);
        button
.setOnClickListener(mBindListener);
        button
= (Button)findViewById(R.id.unbind);
        button
.setOnClickListener(mUnbindListener);
        mKillButton
= (Button)findViewById(R.id.kill);
        mKillButton
.setOnClickListener(mKillListener);
        mKillButton
.setEnabled(false);

        mCallbackText
= (TextView)findViewById(R.id.callback);
        mCallbackText
.setText("Not attached.");
   
}

   
/**
     * Class for interacting with the main interface of the service.
     */

   
private ServiceConnection mConnection = new ServiceConnection() {
       
public void onServiceConnected(ComponentName className,
               
IBinder service) {
           
// This is called when the connection with the service has been
           
// established, giving us the service object we can use to
           
// interact with the service.  We are communicating with our
           
// service through an IDL interface, so get a client-side
           
// representation of that from the raw service object.
            mService
= IRemoteService.Stub.asInterface(service);
            mKillButton
.setEnabled(true);
            mCallbackText
.setText("Attached.");

           
// We want to monitor the service for as long as we are
           
// connected to it.
           
try {
                mService
.registerCallback(mCallback);
           
} catch (DeadObjectException e) {
               
// In this case the service has crashed before we could even
               
// do anything with it; we can count on soon being
               
// disconnected (and then reconnected if it can be restarted)
               
// so there is no need to do anything here.
           
}

           
// As part of the sample, tell the user what happened.
           
Toast.makeText(RemoteServiceBinding.this, R.string.remote_service_connected,
                   
Toast.LENGTH_SHORT).show();
       
}

       
public void onServiceDisconnected(ComponentName className) {
           
// This is called when the connection with the service has been
           
// unexpectedly disconnected -- that is, its process crashed.
            mService
= null;
            mKillButton
.setEnabled(false);
            mCallbackText
.setText("Disconnected.");

           
// As part of the sample, tell the user what happened.
           
Toast.makeText(RemoteServiceBinding.this, R.string.remote_service_disconnected,
                   
Toast.LENGTH_SHORT).show();
       
}
   
};

   
/**
     * Class for interacting with the secondary interface of the service.
     */

   
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
       
public void onServiceConnected(ComponentName className,
               
IBinder service) {
           
// Connecting to a secondary interface is the same as any
           
// other interface.
            mSecondaryService
= ISecondary.Stub.asInterface(service);
            mKillButton
.setEnabled(true);
       
}

       
public void onServiceDisconnected(ComponentName className) {
            mSecondaryService
= null;
            mKillButton
.setEnabled(false);
       
}
   
};

   
private OnClickListener mBindListener = new OnClickListener() {
       
public void onClick(View v) {
           
// Establish a couple connections with the service, binding
           
// by interface names.  This allows other applications to be
           
// installed that replace the remote service by implementing
           
// the same interface.
            bindService
(new Intent(IRemoteService.class.getName()),
                    mConnection
, Context.BIND_AUTO_CREATE);
            bindService
(new Intent(ISecondary.class.getName()),
                    mSecondaryConnection
, Context.BIND_AUTO_CREATE);
            mIsBound
= true;
            mCallbackText
.setText("Binding.");
       
}
   
};

   
private OnClickListener mUnbindListener = new OnClickListener() {
       
public void onClick(View v) {
           
if (mIsBound) {
               
// If we have received the service, and hence registered with
               
// it, then now is the time to unregister.
               
if (mService != null) {
                   
try {
                        mService
.unregisterCallback(mCallback);
                   
} catch (DeadObjectException e) {
                       
// There is nothing special we need to do if the service
                       
// has crashed.
                   
}
               
}

               
// Detach our existing connection.
                unbindService
(mConnection);
                unbindService
(mSecondaryConnection);
                mKillButton
.setEnabled(false);
                mIsBound
= false;
                mCallbackText
.setText("Unbinding.");
           
}
       
}
   
};

   
private OnClickListener mKillListener = new OnClickListener() {
       
public void onClick(View v) {
           
// To kill the process hosting our service, we need to know its
           
// PID.  Conveniently our service has a call that will return
           
// to us that information.
           
if (mSecondaryService != null) {
               
try {
                   
int pid = mSecondaryService.getPid();
                   
// Note that, though this API allows us to request to
                   
// kill any process based on its PID, the kernel will
                   
// still impose standard restrictions on which PIDs you
                   
// are actually able to kill.  Typically this means only
                   
// the process running your application and any additional
                   
// processes created by that app as shown here; packages
                   
// sharing a common UID will also be able to kill each
                   
// other's processes.
                   
Process.killProcess(pid);
                    mCallbackText
.setText("Killed service process.");
               
} catch (DeadObjectException ex) {
                   
// Recover gracefully from the process hosting the
                   
// server dying.
                   
// Just for purposes of the sample, put up a notification.
                   
Toast.makeText(RemoteServiceBinding.this,
                            R
.string.remote_call_failed,
                           
Toast.LENGTH_SHORT).show();
               
}
           
}
       
}
   
};

   
// ----------------------------------------------------------------------
   
// Code showing how to deal with callbacks.
   
// ----------------------------------------------------------------------

   
/**
     * This implementation is used to receive callbacks from the remote
     * service.
     */

   
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
       
/**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */

       
public void valueChanged(int value) {
            mHandler
.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
       
}
   
};

   
private static final int BUMP_MSG = 1;

   
private Handler mHandler = new Handler() {
       
@Override public void handleMessage(Message msg) {
           
switch (msg.what) {
               
case BUMP_MSG:
                    mCallbackText
.setText("Received from service: " + msg.arg1);
                   
break;
               
default:
                   
super.handleMessage(msg);
           
}
       
}

   
};
}