It first introduces the Android serialization mechanism and working principle of Binder, and then analyzes AIDL principle and workflow step by step by creating AIDL for inter-process communication.
preface
Android serialization and Binder are introduced here mainly because AIDL is closely related to these two guys, so let’s get to know them first so that we can better understand how AIDL works.
Android Serialization mechanism
There are generally two methods of serialization in the Android system, which are to implement Serializable interface and Parcelable interface
- Serializable is a serialization interface from Java
- Parcelable is Android’s built-in serialization interface
The two serialization interfaces mentioned above have their own advantages and disadvantages, and we need to use them according to different situations. Serializable is easy to use, just create a version number; Parcelable is a bit more complicated, with four methods to implement. Generally when saving data to SD card or network transmission, Serializable can be recommended, although the efficiency is poor, but fortunately easy to use. In addition, it is recommended to use Parcelable for data transfer at runtime, such as Intent and Bundle, which is optimized at the bottom of Android to achieve high efficiency. So let’s focus on Parcelable.
Parcelable usage and analysis
First we create a Book object and implement the Parcelable interface.
package com.example.zs.ipcdemo.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private String bookId;
private String bookName;
public String getBookId() {
return bookId;
}
public void setBookId(String bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public static Creator<Book> getCREATOR() {
return CREATOR;
}
public Book(String bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readString();
bookName = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(bookId);
dest.writeString(bookName);
}
public void readFromParcel(Parcel in) {
bookId = in.readString();
bookName = in.readString();
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
returnnew Book[size]; }}; }Copy the code
There is a Parcel of readString and writeString methods that return 0 for most of the contents of the serialization. A Parcel is a container for reading data that can be transferred freely with a Binder. I’m not going to go into the details of Parcel. If you’re interested, check out this blog post on The analysis and use of Parcel in Android
Binder
Binder is simply a class in Android that inherits the IBinder interface. Binder from different perspectives.
-
Binder is a cross-process communication method in Android from an IPC perspective. Binder can also be thought of as a virtual physical device driven by /dev/binder, a communication method not found in Linux.
-
From the perspective of the Android Framework, Binder is the bridge between ServiceManager managers (ActivityManager, WindowManager, etc) and corresponding ManagerServices.
-
At the Android application layer, Binder is the communication medium between the client and the server. When you bindService, the server returns a Binder object containing the service call. The client can obtain the services or data provided by the server, including normal services and AIDL-based services.
#### Analysis of Binder composition
The ServiceManager in the picture is responsible for registering the Binder Server with a container, and when the client calls the Server, the Binder driver goes to the Binder Server container to find the Server you want to call and tell it to do that and that. #### Analyze Binder communication process
As you can see from the figure, the Client cannot call Server’s Add method directly because they are in different processes. Binder is needed for this purpose.
- The first step is to register the Server in the ServiceManager container.
- In the second part, the Client needs to get the Server object before calling the Server add method, but the ServiceManager does not return the actual Server object to the Client. Instead, it returns a Proxy object from the Server to the Client, the Proxy.
- The third Client calls the Proxy add method. The ServiceManager calls the Server Add method for it and returns the result to the Client. Let’s create a simple AIDL and then step by step analyze how it works. #### Create the Book class. Create an aiDL package under the main package to store the data used by the process communication. The Book class inherits Parcelable interface (reason: AIDL can only transmit classes that inherit Parcelable interface).
package com.example.zs.ipcdemo.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private String bookId;
private String bookName;
public String getBookId() {
return bookId;
}
public void setBookId(String bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public static Creator<Book> getCREATOR() {
return CREATOR;
}
public Book(String bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
bookId = in.readString();
bookName = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(bookId);
dest.writeString(bookName);
}
public void readFromParcel(Parcel in) {
bookId = in.readString();
bookName = in.readString();
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
returnnew Book[size]; }}; }Copy the code
#### create book. aidl, iBookManager. aidl why book. aidl, because aiDL can only call Book if the class is declared in aiDL. So for AIDL to be able to pass custom classes, 1) inherit the Parcelable interface and 2) create an.aidl file with the same name to declare itself.
package com.example.zs.ipcdemo.aidl;
parcelable Book;
Copy the code
Ibookmanager.aidl sets the interface that the client is allowed to call.
package com.example.zs.ipcdemo.aidl;
import com.example.zs.ipcdemo.aidl.Book;
interface IBookManager{
List<Book> getBookList();
void addBook(in Book book);
}
Copy the code
One thing to notice here is that the addBook method has an in type, so let’s talk a little bit about the difference between an IN type in an AIDL file and an out type and an inout data.
- In: parameter input of the client: Yes the value of the argument is assigned to the row parameter. Modification of the row parameter does not affect the value of the argument.
- Out: parameter input on the server side: After passing, the line argument and the argument are the same object, but their names are different. Changes to the line argument affect the value of the argument.
- Inout: This can be called an input/output parameter, which can be entered by the client or the server. After the client input parameters to the server, the server can modify the parameters. Finally, the client gets the parameters output by the server.
App \build\generated\source\aidl\debug automatically generates IBookManger. Let’s take a look at what IBookManger. Java looks like.
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\\GitHub\\IPCDemo\\app\\src\\main\\aidl\\com\\example\\zs\\ipcdemo\\aidl\\IBookManager.aidl
*/
package com.example.zs.ipcdemo.aidl;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.zs.ipcdemo.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.example.zs.ipcdemo.aidl.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.zs.ipcdemo.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.zs.ipcdemo.aidl.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if(((iin ! = null) && (iin instanceof com.example.zs.ipcdemo.aidl.IBookManager))) {return ((com.example.zs.ipcdemo.aidl.IBookManager) iin);
}
return new com.example.zs.ipcdemo.aidl.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_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.zs.ipcdemo.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.example.zs.ipcdemo.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.zs.ipcdemo.aidl.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.zs.ipcdemo.aidl.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;
}
@Override
public java.util.List<com.example.zs.ipcdemo.aidl.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<com.example.zs.ipcdemo.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.zs.ipcdemo.aidl.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.example.zs.ipcdemo.aidl.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(); } finally { _reply.recycle(); _data.recycle(); } } @Override public void registerListener(com.example.zs.ipcdemo.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((listener ! = null)) ? (listener.asBinder()) : (null))); mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public void unregisterListener(com.example.zs.ipcdemo.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeStrongBinder((((listener ! = null)) ? (listener.asBinder()) : (null))); mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public java.util.List<com.example.zs.ipcdemo.aidl.Book> getBookList() throws android.os.RemoteException; public void addBook(com.example.zs.ipcdemo.aidl.Book book) throws android.os.RemoteException; }Copy the code
The IBookManager file contains two interfaces to iBookManager. aidl, as well as Stub and Proxy classes that implement the IBookManager interface. Stub is defined in the IBookManager interface, and Proxy is defined in the Stub class.
Client Execution Process
1. When the service connection is established, the client executes the code. IBookManager. Stub. AsInterface (service) the role of this method is to determine the incoming parameters IBinder objects, and if they are in the same process if not wrap the IBinder parameters into a Proxy object, Stub sum is called and Proxy getBookList() is indirectly called. Let me look at the code.
Private ServiceConnection Connection = newServiceConnection() { @Override public void onServiceConnected(final ComponentName name, IBinder service) {// The function of the asInterface method is to check whether the IBinder object is in the same process as the IBinder object: // No: The IBinder argument is wrapped as a Proxy object, and the Stub method is called, Indirect call the Proxy method IBookManager bookManager. = IBookManager Stub. AsInterface (service); try { mBookManager = bookManager; bookManager.getBookList(); Log.d(TAG,"get books count:"+ bookManager.getBookList().size()); bookManager.registerListener(arrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mBookManager = null; }};Copy the code
public static com.example.zs.ipcdemo.aidl.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
returnnull; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Check whether the IBinder object is in the same process as the IBinder objectif(((iin ! = null) && (iin instanceof com.example.zs.ipcdemo.aidl.IBookManager))) {return ((com.example.zs.ipcdemo.aidl.IBookManager) iin);
}
return new com.example.zs.ipcdemo.aidl.IBookManager.Stub.Proxy(obj);
}
Copy the code
2. In the sumgetBookList() method, the Proxy uses Parcelable to prepare the data, writes the function name and function parameters to _data, and lets _reply receive the function return value. Finally, IBinder’s transact() method is used to pass data to Binder servers.
@Override public java.util.List<com.example.zs.ipcdemo.aidl.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<com.example.zs.ipcdemo.aidl.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); Transact (stub.transaction_getbooklist, _data, _reply, 0); // Notify the server to call the method mremote.transact (stub.transaction_getbooklist, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.example.zs.ipcdemo.aidl.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); }return _result;
}
Copy the code
Server Execution Process
The Server receives the data from the Client process via the onTransact() method, including the function name and function parameters, finds the corresponding function, in this case getBookList(), feeds the parameters, gets the result, and returns the result. So the onTransact() function goes through the process of reading the data, executing the function to be called, and writing the result back to the data.
@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; } // the client calls the directive getBookList()caseTRANSACTION_getBookList: { data.enforceInterface(DESCRIPTOR); / / call the local service getBookList () method in Java. The util. List < com. Example. Zs. Ipcdemo. Aidl. Book > _result = this. GetBookList (); reply.writeNoException(); WriteTypedList (_result);return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.example.zs.ipcdemo.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.zs.ipcdemo.aidl.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
Copy the code
The above is the specific principle of AIDL and the analysis of the work process, I hope to help you. The code has been updated at GitHub: github.com/christian-z…