Simple understanding of Android Binder communication (AIDL)

Keywords: Android DNS Java network

1. Binder Analogue TCP/IP

We know that the fundamental reason two functions in the same program can be called directly is because they are in the same memory space.Conversely, two different processes cannot access functions or variables directly from each other's internal memory address. Is there an "indirect" method?That's what Binder does.

Binder is the most widely used IPC mechanism in Android.The elements that make up a Binder communication are as follows:

Binder drive
Service Manager
Binder Client
Binder Server

If you are familiar with TCP/IP networks, you will find many similarities between the two.

Binder drive -- Router Router
Service Manager -- NDS
Binder Client -- Client
Binder Server -- Server

A typical service connection process in TCP/IP is as follows:

1) Client queries DNS for the IP address of Google.com
Obviously, the Client must know the IP address of the DNS before it can query it.DNS IP setup is completed before the client can access the network.Of course, if the Client already knows the IP of the Server, then it is perfectly possible to connect directly to the Server across this step.For example, the Windows operating system provides a hosts file to query the relationship between common web address domain names and their IP addresses.When a user needs to visit a web address, the system will first determine from this file whether the corresponding IP for this domain name already exists.If you do, you don't have to bother with DNS queries to speed up access.

2) DNS returns query results to Client

3) Client initiates the connection

In this process, we can know:

1) IP addresses are their credentials for communicating with each other;

2) The above process does not specifically mention the role of Router as it is the basis for building a communication network that can be filled out by users
Target IP correctly sends packets to bits.

3) The NDS role is not required; it appears to help people associate complex, difficult-to-remember IP addresses with more readable domain names and provide query capabilities.Clients can use DNS only if they have configured the IP address of the DNS server.

Understanding the role of each module in network communication, we compare it with Binder.

Process 1 (client) communicates with process 2 (server) because they are cross-process.
Therefore, the Binder driver (router) must be used to properly deliver the request to the other party's process.
Processes that participate in the communication need to have a unique identity (IP address) issued by Binder.
This flag is obtained from the Service Manager (DNS).

Since Service Manager is a DNS, what is its "IP Address"?The Binder mechanism specifically specifies this:

Service Manager's unique flag during Binder communication is always 0

2. Binder communication process (transact and onTransact)

Any service must be registered with SM(Service Manager) before it can be used.

SM's main job is to initialize Binder, turn on/dev/binder devices, and map 128K bytes of space in memory for Binder.
It maintains a dead loop in which Binder Driver in the kernel is constantly read to see if there is any readable content, that is, whether there is an operation request for a service, and if there is, the svcmgr_handler callback is called to process the requested operation.

The communication between the client and the server involves two Binder communications.
1) Clients query SM for the existence of a service, and if there is a proxy Binder (Unique Identity) obtaining the service, this is a Binder communication;
2) The client invokes the service method through the proxy binder, which is the second Binder communication.

For example, if I want to communicate with AMS, I first get the unique logo of the AMS service object:

IBinder b = ServiceManager.getService("activity");

Returns an IBinder object that is the unique identifier (IP address) issued by the Binder as described above.

IBinder is an interface where the primary API is transact(),

public boolean transact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException;

Clients get the IBinder object, which is the only flag of the server, and can initiate requests to the server through its transact method.

The first parameter indicates the intent of this request to execute.
IBinder defines several common commands such as INTERFACE_TRANSACTION and PING_TRANSACTION.
The identity value of the command you use needs to be between FIRST_CALL_TRANSACTION and LAST_CALL_TRANSACTION.
This parameter is agreed upon between the client and the server.

The second parameter represents the data sent to the server and cannot be null.

The third parameter represents the data returned by the server and can be null.

The fourth parameter flags has only 0 and FLAG_ONEWAY(=1), and the default cross-process operation is synchronous.
So the execution of the transact() method is blocked; when FLAG_ONEWAY is specified,
A transact() representing a Client is a one-way call that returns immediately after execution without waiting for the server to return.

To support remote calls, the server should derive a class from the Binder class.The Binder class inherits from IBinder, and the Binder class has an onTransact() method.In this way, the server responds to the client's request.

3. AIDL Communication Principles

AIDL communication is the encapsulation of Binder communication.Because you want to implement IBinder to support remote calls, you should derive a class from the Binder class.With AIDL, it automatically generates a derived class of Binder, which can be used directly.

For example, define an AIDL file:

package com.example.aidl;
interface AB {
    void a(int i);
}

The compiler automatically generates an AB.java file that cannot be modified:

public interface AB extends android.os.IInterface{
    public static abstract class Stub extends Binder implements AB{//Server
        ...........
        private static class Proxy implements AB{//Client
            ...........
        }
        static final int TRANSACTION_a = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }
    public void a(int i) throws android.os.RemoteException;
}

There are two main internal classes in this Java file, representing the client and the server:

Represents the client's internal class (nested within the service side) Proxy: The client calls the a() method, in which we can see that the request is made to the service side through the IBinder object:
mRemote.transact(Stub.TRANSACTION_a, _data, _reply, 0);

First parameter: Look at the construction of code.
static final int TRANSACTION_a = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

The fourth parameter, 0, indicates that the transact method defaults to synchronous blocking.
You can add oneway before the method of the aidl interface, such as:
oneway void a(int i);
Represents a one-way call to this method that returns immediately after execution without waiting for the server to return.
The transact method in the generated java file is called as follows:
mRemote.transact(Stub.TRANSACTION_a, _data, null, IBinder.FLAG_ONEWAY);

private static class Proxy implements AB{
      private IBinder mRemote;
      Proxy(IBinder remote){
            mRemote = remote;
      }
      @Override
      public IBinder asBinder(){
            return mRemote;
      }
      public String getInterfaceDescriptor(){
             return DESCRIPTOR;
      }

      @Override
      public void a(int i) throws RemoteException{
          Parcel _data = Parcel.obtain();
          Parcel _reply = Parcel.obtain();
          try {
               _data.writeInterfaceToken(DESCRIPTOR);
               _data.writeInt(i);
               mRemote.transact(Stub.TRANSACTION_a, _data, _reply, 0);
               _reply.readException();
          }finally {
              _reply.recycle();
               _data.recycle();
          }
      }
}

Represents the internal class Stub on the server side:

The onTransact method responds to the request and executes the a() method through code.It is conceivable to implement this method on the server side.

The asInterface(IBinder obj) method returns a client object (proxy).

The asBinder() method returns an IBinder object.

public static abstract class Stub extends Binder implements AB{
      private static final String DESCRIPTOR = "com.example.administrator.myapplication.AB";

      public Stub() {
          this.attachInterface(this, DESCRIPTOR);
      }

     public static AB asInterface(IBinder obj){
            if ((obj==null)) {
                return null;
            }
            IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&&(iin instanceof AB))) {
                return ((AB)iin);
            }
            return new AB.Stub.Proxy(obj);
    }
    @Override
    public android.os.IBinder asBinder(){
         return this;
    }
    @Override
    public boolean onTransact(int code,Parcel data, Parcel reply, int flags) throws RemoteException{
        switch (code){
           case INTERFACE_TRANSACTION:{
                reply.writeString(DESCRIPTOR);
                return true;
           }
           case TRANSACTION_a:{
                data.enforceInterface(DESCRIPTOR);
                int _arg0;
                _arg0 = data.readInt();
                this.a(_arg0);
                reply.writeNoException();
                return true;
           }
       }
       return super.onTransact(code, data, reply, flags);
    }
    private static class Proxy implements AB{
        . . . . . 
    }
    static final int TRANSACTION_a = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}

So when we use AIDL to communicate, we need to define the same AIDL file on both the client and the server, and generate the same Java file.
It is important to note that both client and server AIDL file package names are identical.

4. Examples of AIDL Communications

Client:

1. aidl file:

package com.example.aidl;
interface AB {
    void a();
}

2,ServiceConnection :

ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // TODO Auto-generated method stub
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            AB ab = AB.Stub.asInterface(service);
            try {
                ab.a();
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
};

3. Binding services:

Intent in = new Intent();
in.setPackage("com.example.server");
in.setAction("AB");
bindService(in, conn, Context.BIND_AUTO_CREATE);

Server: same as client's aidl package name

1. aidl file:

package com.example.aidl;
interface AB {
    void a();
}

2. Define a Service:

<service android:name=".MyService" android:exported="true">
            <intent-filter>
                <action android:name="AB"></action>
            </intent-filter>    
</service>

3. Implement aidl class

AB ab = new AB.Stub() {     
   @Override
   public void a() throws RemoteException {
      System.out.println("aaaaaaaaaaaaaaaaaaaa");       
   }
};

4. Binding of Response Services

@Override
public IBinder onBind(Intent intent) {
    return ab.asBinder();
}

Posted by mrdance on Wed, 17 Jul 2019 09:47:03 -0700