English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
AIDL di Android, meccanismo di comunicazione tra processi di Android, ecco una raccolta di conoscenze su AIDL per aiutare tutti a imparare e comprendere questa parte di conoscenza!
Cos'è AIDL
AIDL, abbreviazione di Android Interface Definition Language, è noto anche come Linguaggio di Descrizione dell'Interfaccia Android. Suona molto complesso, ma in realtà la sua essenza è un strumento ausiliario per la generazione di interfacce di comunicazione tra processi. Esiste sotto forma di file .aidl, e tutto ciò che il sviluppatore deve fare è definire l'interfaccia di comunicazione tra processi in questo file. Durante la compilazione, l'IDE genererà automaticamente i file .java utilizzabili nel progetto in base al nostro file di interfaccia .aidl, il che è simile a quello che chiamiamo "dolcezza sintattica".
La sintassi di AIDL è la stessa della sintassi di java, con alcune differenze minime nella dichiarazione dei pacchetti. In java, se due classi si trovano nello stesso pacchetto, non è necessario eseguire l'operazione di dichiarazione del pacchetto, ma in AIDL, è necessario eseguire la dichiarazione di importazione.
Dettagli su AIDL
Immaginiamo una scena: abbiamo un sistema di gestione dei libri, questo sistema viene implementato tramite il modello CS. Le funzioni di gestione specifiche vengono implementate dal processo server, il client deve solo chiamare l'interfaccia corrispondente.
Quindi prima definiamo l'interfaccia ADIL di questo sistema di gestione.
Abbiamo creato una nuova directory aidl in /rc, nella directory ci sono tre file Book.java, Book.aidl, IBookManager.aidl.
package com.example.aidl book public class Book implements Parcelable { int bookId; String bookName; public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } ... }
package com.example.aidl;
Parcelable Book;
package com.example.aidl; import com.example.aidl.Book; inteface IBookManager { List<Book> getBookList(); void addBook(in Book book); }
Di seguito, spieghiamo questi tre file rispettivamente:
Book.java è la classe entità che abbiamo definito, che implements l'interfaccia Parcelable, in modo che la classe Book possa essere trasportata tra processi.
Book.aidl è la dichiarazione di questa classe entità nell'AIDL.
IBookManager è l'interfaccia di comunicazione tra il server e il client. (Attenzione, negli interfacce AIDL, oltre ai tipi di base, è necessario aggiungere una direzione davanti ai parametri, in indica il tipo di parametro in ingresso, out indica il tipo di parametro in uscita, inout indica il tipo di parametro di input/output)
Il compilatore ha compilato, Android Studio ha generato automaticamente un file .java per il nostro progetto, questo file contiene tre classi, queste tre classi sono IBookManager, Stub e Proxy, queste tre classi sono di tipo statico, possiamo separarle completamente, le definizioni delle tre classi sono come segue:
IBookManager
public interface IBookManager extends android.os.IInterface { public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException; public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException; }
Stub
public static abstract class Stub extends android.os.Binder implements net.bingyan.library.IBookManager { private static final java.lang.String DESCRIPTOR = "net.bingyan.library.IBookManager"; static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); /** * Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an net.bingyan.library.IBookManager interface, * generating a proxy if needed. http://www.manongjc.com/article/1501.html */ public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) { return ((net.bingyan.library.IBookManager) iin); } return new net.bingyan.library.IBookManager.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); net.bingyan.library.Book _arg0; if ((0 != data.readInt())) { _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<net.bingyan.library.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } } return super.onTransact(code, data, reply, flags); } }
Proxy
private static class Proxy implements net.bingyan.library.IBookManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. http://www.manongjc.com/article/1500.html */ @Override public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } _reply.recycle(); _data.recycle(); } } @Override public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<net.bingyan.library.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR); } _reply.recycle(); _data.recycle(); } return _result; } }
对生成的这三个类的说明如下:
@Override public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR) if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0); _reply.readException(); } _reply.recycle(); _data.recycle(); } } @Override /* http://www.manongjc.com/article/1547.html */ public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<net.bingyan.library.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR); } _reply.recycle(); _data.recycle(); } return _result; }
Sono implementati automaticamente dal compilatore, queste due metodologie condividono molte somiglianze e posso rivelarvi qui: queste due metodologie sono le finestre di chiamata del processo client al processo server. All'inizio di queste due metodologie, entrambe definiscono due oggetti Parcel (nome cinese: imballaggio). La classe Parcel sembra familiare a noi, sì, i parametri writeToParcel() e createFromParcel() della classe Book sono di tipo Parcel, riguardo a questa classe, la documentazione spiega come segue:
Contenitore per un messaggio (dati e riferimenti agli oggetti) che può essere trasmesso attraverso un IBinder. Un Parcel può contenere sia dati appiattiti che verranno ripristinati dall'altro lato dell'IPC (utilizzando i vari metodi qui per scrivere tipi specifici, o l'interfaccia generale {@link Parcelable}), sia riferimenti a oggetti vivi {@link IBinder} che faranno sì che l'altro lato riceva un proxy IBinder connesso con l'oggetto IBinder originale nel Parcel.
Proxy è un contenitore che può trasmettere messaggi tramite IBinder. Un Parcel può contenere dati serializzabili, che verranno deserializzati dall'altro lato dell'IPC; può anche contenere riferimenti a oggetti IBinder, il che farà sì che l'altro lato riceva un'istanza proxy di IBinder connessa con l'oggetto IBinder originale nel Parcel.
Di seguito, utilizziamo le immagini per illustrare chiaramente:
Il meccanismo di comunicazione dei processi in Android tramite AIDL
Come illustrato nella figura, possiamo vedere chiaramente che il server comunica con il client utilizzando Parcel come pacchetto di dati attraverso Binder. Il pacchetto di dati è l'oggetto serializzato.
Come descritto sopra, entrambi i metodi definiscono due oggetti Parcel, rispettivamente chiamati _data e _reply, per parafrasare, dal punto di vista del client, _data è il pacchetto di dati inviato dal client al server, mentre _reply è il pacchetto di dati inviato dal server al client.
Dopo di che si inizia a utilizzare questi due oggetti per comunicare con il server, possiamo osservare che entrambi i metodi contengono una chiamata al metodo mRemote.transact(), che ha quattro parametri, il significato del primo parametro sarà spiegato in seguito, il secondo parametro _data è responsabile dell'invio di pacchetti di dati al server come i parametri del metodo dell'interfaccia, il terzo parametro _reply è responsabile della ricezione di pacchetti di dati dal server come i valori di ritorno del metodo dell'interfaccia. Questa riga di codice è solo una chiamata di metodo semplice, ma è la parte più coreana della comunicazione AIDL, che in realtà esegue una chiamata di metodo remoto (il client chiama il metodo同名 dello Stub del server attraverso l'interfaccia Proxy locale esposta), quindi puoi immaginare che sia un'operazione che richiede molto tempo.
Nel nostro esempio:
1. Il metodo addBook(Book book) ha bisogno di aiuto da parte di _data per inviare il parametro Book:book al server, il modo di invio è imballare Book attraverso il metodo writeToParcel(Parcel out) implementato, come puoi immaginare, _data è in realtà il parametro out, ricordi l'implementazione di questo metodo in Book? Abbiamo imballato i campi di Book uno per uno nel Parcel.
2. Il metodo getBookList() ha bisogno di aiuto da parte di _reply per ricevere il valore di ritorno List<Book>:books dal server, la tecnica utilizzata è quella di passare il campo statico CREATOR di Book come parametro al metodo createTypedArrayList() di _reply. Ricordi il CREATOR di Book? All'epoca ti sei forse chiesto come utilizzare questo campo statico? Ora tutto è chiaro, dobbiamo contare su questo oggetto (che possiamo chiamare 'deserializzatore' per facilitarne la comprensione) per deserializzare i dati del server e rigenerare oggetti o array serializzabili. È chiaro che CREATOR ha utilizzato _reply per generare List<Book>:books.
Certo, questi due metodi non solo trasmettono oggetti, ma trasmettono anche alcune informazioni di convalida, che non dobbiamo approfondire, ma è importante notare che l'ordine di imballaggio e di smistamento del Parcel deve essere rigorosamente corrispondente. Ad esempio, se il primo imballaggio è int:i, il primo smistamento dovrebbe essere anche questo valore intero. Questo significa che se il primo chiamato durante l'imballaggio è Parcel.writeInt(int), durante lo smistamento dovrebbe essere chiamato Parcel.readInt().
Arrivato qui, la spiegazione del Proxy del client è terminata, ora vediamo lo Stub del server.
La classe Stub ha implementato uno dei metodi di IBookManager, questo è molto semplice, è semplicemente restituire se stesso, perché Stub deriva direttamente da Binder, e Binder deriva da IBinder, quindi non ci sono problemi. Potresti chiedere: ci sono ancora due metodi non implementati? Questi due metodi sono i metodi dell'interfaccia definiti da noi, che lasciamo al processo del server per implementare, il che significa che in futuro dobbiamo definire un implementatore di Stub nel processo del server. Di seguito analizziamo due metodi importanti della classe Stub:
IBookManager asInterface(IBinder obj)
public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) { return ((net.bingyan.library.IBookManager) iin); } return new net.bingyan.library.IBookManager.Stub.Proxy(obj); }
Il ruolo di questo metodo è quello di convertire la classe Stub in un'interfaccia IBookManager, nel metodo c'è una condizione: se il processo del server e il processo del client sono lo stesso processo, allora si convertirà direttamente la classe Stub in IBookManager tramite la conversione di tipo; se non sono lo stesso processo, allora si convertirà Stub in IBookManager tramite l'oggetto proxy Proxy. Perché farlo, sappiamo che se il processo del server e il processo del client non sono lo stesso processo, le loro memorie non possono essere condivise e non possono comunicare attraverso il modo generale, ma se noi stessi implementiamo il modo di comunicazione tra processi, il costo per lo sviluppatore comune è troppo alto, quindi il compilatore ci aiuta a generare uno strumento di encapsulamento della comunicazione tra processi, ovvero questo Proxy, questa classe encapsula il meccanismo di comunicazione tra processi di basso livello e simultaneously esponendo solo i metodi dell'interfaccia, il client deve chiamare questi due metodi per realizzare la comunicazione tra processi (che è in realtà la chiamata remota del metodo) senza dover conoscere i dettagli interni.
Con questo metodo, possiamo utilizzare il suo aiuto sui client per convertire una variabile di tipo IBinder in un'interfaccia definita da noi, IBookManager, la cui scena di utilizzo sarà spiegata in esempi successivi.
onTransact(int code, Parcel data, Parcel reply, int flags)
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); net.bingyan.library.Book _arg0; if ((0 != data.readInt())) { _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; /* http://www.manongjc.com/article/1499.html */ } case TRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); java.util.List<net.bingyan.library.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } } return super.onTransact(code, data, reply, flags); }
Non è familiare anche questo metodo? Ne vediamo uno simile in Proxy, chiamato transact(int, Parcel, Parcel, int), con parametri identici e sono entrambi metodi di Binder, quindi cosa c'è di comune tra loro?
Come ho detto prima, transact() esegue una chiamata remota. Se transact() è l'iniziatore della chiamata remota, onTransact() è la risposta alla chiamata remota. Il processo reale è che il client invia una chiamata remota al metodo, il sistema Android risponde e gestisce questa chiamata tramite codice di basso livello, poi chiama il metodo onTransact() del server, estrae i parametri del pacchetto dati, li consegna al metodo同名o implementato dal server, infine impacchetta e restituisce il valore di ritorno al client.
Occorre notare che onTransact() viene eseguito nel pool di thread Binder del processo server, il che significa che se dobbiamo aggiornare l'UI nel metodo onTransact(), dobbiamo utilizzare un Handler.
Il significato del primo parametro di queste due funzioni è l'identificatore del metodo dell'interfaccia AIDL. Nel Stub, sono definiti due costanti come identificatori per queste due funzioni:
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
Se code == TRANSACTION_addBook, significa che il client ha chiamato addBook(); se code == TRANSACTION_getBookList, il client ha chiamato getBookList(), che poi viene elaborato dal metodo del server corrispondente. Ecco un diagramma che rappresenta l'intero processo di comunicazione:
Il meccanismo di comunicazione dei processi in Android tramite AIDL
Dopo aver compreso l'intero processo di AIDL, passiamo all'applicazione di AIDL nei programmi Android.
L'uso di AIDL
Credo che tutti dovrebbero essere abbastanza familiarizzati con l'uso di Service. Anche se chiamato 'servizio' e eseguito in background, di default si esegue nella thread principale del processo predefinito. In realtà, far eseguire il Service nel processo predefinito è un po' sfruttare le risorse. Molti servizi di sistema di Android si eseguono in processi separati, disponibili per altre applicazioni, come il servizio di gestione delle finestre. Questo metodo ha il vantaggio di permettere a più applicazioni di condividere lo stesso servizio, risparmiare risorse e facilitare la gestione centralizzata dei client. Tuttavia, è necessario prestare attenzione ai problemi di sicurezza delle thread.
Quindi, procediamo a implementare un semplice sistema di gestione delle biblioteche con architettura CS utilizzando AIDL.
Prima di tutto, definiamo il server:
BookManagerService
public class BookManagerService extends Service { private final List<Book> mLibrary = new ArrayList<>(); private IBookManager mBookManager = new IBookManager.Stub() { @Override public void addBook(Book book) throws RemoteException { synchronized (mLibrary) { mLibrary.add(book); Log.d("BookManagerService", "ora la nostra biblioteca ha " + mLibrary.size() + " libri"); } } @Override public List<Book> getBookList() throws RemoteException { return mLibrary; } }; @Override /* http://www.manongjc.com/article/1496.html */ public IBinder onBind(Intent intent) { return mBookManager.asBinder(); } }
<service android:process=":remote" android:name=".BookManagerService"/>
Sul lato server, abbiamo definito la classe BookManagerService, al cui interno abbiamo creato l'oggetto Stub del server e abbiamo implementato i due metodi dell'interfaccia AIDL necessari per definire la strategia di gestione dei libri del server. Nel metodo onBind(), restituiamo l'oggetto IBookManager come IBinder. Sappiamo che quando si binda un servizio, il sistema chiama il metodo onBinder() per ottenere l'oggetto IBinder del server e lo trasforma in un oggetto IBinder del client e lo passa al client, anche se l'IBinder del server e l'IBinder del client sono due oggetti IBinder, ma sono lo stesso oggetto in basso. Quando registriamo il Service in xml, gli assegniamo un nome di processo, in modo che il Service possa eseguire in un processo separato.
Guarda ora l'implementazione del client:}
Client
public class Client extends AppCompatActivity { private TextView textView; private IBookManager bookManager; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.library_book_manager_system_client); Intent i = new Intent(Client.this, BookManagerService.class); bindService(i, conn, BIND_AUTO_CREATE); Button addABook = (Button) findViewById(R.id.button); addABook.setOnClickListener(v -> { if (bookManager == null) return; try { bookManager.addBook(new Book(0, "book")); textView.setText(getString(R.string.book_management_system_book_count, String.valueOf(bookManager.getBookList().size()))); } catch (RemoteException e) { e.printStackTrace(); } }); textView = (TextView) findViewById(R.id.textView); } private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d("Client -->", service.toString()); bookManager = IBookManager.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { Log.d("Client", name.toString()); } }; }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:weightSum="1" android:gravity="center"> <Button android:text="http://www.manongjc.com/article/1495.html" android:layout_width="111dp" android:layout_height="wrap_content" android:id="@+id/button" /> <TextView android:layout_marginTop="10dp" android:text="@string/book_management_system_book_count" android:layout_width="231dp" android:gravity="center" android:layout_height="wrap_content" android:id="@+id/textView" /> </LinearLayout>
Il nostro client è un Activity, nella funzione onCreate() viene effettuata la binding del servizio, nel metodo bindService() c'è un parametro ServiceConnection:conn, poiché la binding del servizio è eseguita in modo asincrono, il ruolo di questo parametro è di essere chiamato di ritorno quando la binding del servizio è riuscita, ha due metodi di callback: uno è chiamato quando la connessione al servizio è riuscita, l'altro è chiamato quando la connessione con il server è interrotta. Oggi ci interessiamo principalmente al metodo onServiceConnected(), qui abbiamo fatto una sola cosa: convertire l'oggetto IBinder passato dal server in un'interfaccia AIDL, abbiamo definito il campo IBookManager:bookManager per mantenere il riferimento. In questo modo, possiamo effettuare chiamate remote dei metodi tramite questo bookManager. Abbiamo registrato un evento sul pulsante del client: ogni volta che viene cliccato, viene aggiunto un libro al server e viene visualizzato il numero di libri attualmente presenti nella biblioteca.
Ora vediamo l'effetto di esecuzione del programma:
Ogni volta che clicchiamo sul pulsante, aggiungiamo con successo un libro al server, il che significa che abbiamo avuto successo nella comunicazione inter-processuale tramite AIDL.
Grazie per la lettura, speriamo di essere utili a tutti, grazie per il supporto del nostro sito!
Dichiarazione: il contenuto di questo articolo è stato tratto da Internet, il copyright spetta agli autori, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, questo sito non possiede il diritto di proprietà, non è stato editato manualmente e non assume alcuna responsabilità legale. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a: notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare, fornendo prove pertinenti. Una volta verificata, questo sito eliminerà immediatamente il contenuto sospetto di violazione del copyright.