One, the introduction

As for interprocess communication in Android, we know that it can probably be done in the following ways:

  • Bundle: Communication between four components
  • File: File sharing
  • ContentProvider: Data is shared between applications
  • AIDL:Bindermechanism
  • MessagerBased on:AIDL,Handlerimplementation
  • Socket: to establish aC/SCommunication model

This article mainly explores AIDL and Socket implementation methods, and analyzes the cross-process communication mechanism of Binder based on the code generated by AIDL in daily use.

Complete code for this article: AndroidIPCDemo

Second, the use ofAIDLandSocketcommunicate

First, let’s talk about the communication model we will implement in a moment, which is roughly as follows:

Then look at the directory structure:

Look at IMyAidlInterface aidl, here in defining method is names need to be aware of when it is not same, package need to manually import, may be because the aidl file when parsing does not distinguish parameter types, cause I have been compile errors in setting method of the same name, do I have to find other problems, So be aware of this:

// IMyAidlInterface.aidl
package com.project.horselai.bindprogressguarddemo;
// Package that needs to be imported manually
import com.project.horselai.bindprogressguarddemo.IMyAidlInterfaceCallback;
import com.project.horselai.bindprogressguarddemo.MyMessage;
interface IMyAidlInterface { 

    void sendMessage(String msg); 
    void sendMessageObj(in MyMessage msg); 
    int getProcessId(a); 
    void registerCallback(IMyAidlInterfaceCallback callback); 
    void unregisterCallback(IMyAidlInterfaceCallback callback);
} 
Copy the code

And then the IMyAidlInterfaceCallback. Aidl:

// IMyAidlInterfaceCallback.aidl
package com.project.horselai.bindprogressguarddemo;
  
interface IMyAidlInterfaceCallback {

    void onValueCallback(int value);
} 
Copy the code

Finally the MyMessage. Aidl:

// MyMessage.aidl
package com.project.horselai.bindprogressguarddemo;

parcelable MyMessage;
Copy the code

Other code is too long to post, see AndroidIPCDemo.

The demo diagram is as follows. Let’s run and see.

One phenomenon is that the ServiceConnection does not break after the unbindService call, so it can be sent and received if the message is sent again.

Third, fromAIDLGenerate class source code to understandBindermechanism

1. Let’s start with some theoretical knowledge about IBinder

Official text: IBinder

IBinder, as the basic interface for remote objects, is the core of a lightweight remote call mechanism designed for high-performance cross-process and in-process invocation. IBinder describes an abstract protocol for interacting with remote objects, which should be inherited from Binder and implemented directly.

The key API for IBinder is Transact (), which is used in conjunction with binder.ontranscat () to send a request to an IBinder object when the transcat() method is invoked and received in binder.ontranscat (), Transcat() is executed synchronously. After transcat() is executed, transcat() will wait for the other binder.ontranscat () method to return. This behavior is inevitable when executed in the same process, but when executed between different processes, The underlying IPC mechanism also ensures the same behavior.

The transact() method sends data of type Parcel. A Parcel is a generic data buffer that contains meta-data describing what it is hosting, which is used to manage IBinder object references in the buffered data. These references can therefore be saved as buffered data to be passed to other processes. This ensures that the IBinder can be written to a Parcel and sent to other processes. If other processes send the same IBinder reference back to the source process, the source process receives the same IBinder object. This feature enables IBinder/Binder objects to act as unique identifiers between processes (as server tokens or for other purposes).

The system maintains A transaction thread pool for each running process. The threads in the thread pool are used to distribute all IPC transactions from other processes. For example, when process A conducts IPC with process B (A is the sending process), A calls Transact () to send the transaction to process B. The called thread in A is blocked in Transact (), and if the available thread pool thread in process B receives A transaction from A, the binder.ontranscat () of the target object (process A) is called and A Parcel is replied. Upon receiving the reply from process B, the thread executing transact() in process A terminates the block and continues with the other logic.

Binder systems also support cross-process recursion, for example, if process A executes A transaction to process B, and then process B executes ibinder.transact () implemented by process A while processing the received transaction. The thread in process A that is waiting for the completion of the original transaction will be used to perform the binder.ontranscat () reply for the object called by process B. This behavior ensures that the recursive mechanism behaves identically when calling Binder objects remotely and locally.

You can determine whether a remote object is available in the following three ways:

  • When calling a process that does not existIBinder.transact()Thrown whenRemoteExceptionThe exception;
  • callpingBinder()returnfalse“Indicates that the remote process does not exist.
  • uselinkToDeath()Approach toIBinderSign up for aIBinder.DeathRecipientWhen the host process is killed, it will be notified through this listener.

2. AIDL generation class source code analysis

Let’s look at the structure of the generated class:

The IMyAidlInterface interface is the direct code generation for defining the AIDL interface, while imyaidlInterface.stub is the abstract class that implements the IMyAidlInterface interface and implements the onTranscat() method. But it does not have specific implementation IMyAidlInterface method, but the implementation of this part to the IMyAidlInterface. The Stub. The Proxy.

OK, let’s break it down. Start by locating Stub#asInterface, you can see that it is mainly responsible for distinguishing between local and cross-process communication.

public static com.project.horselai.bindprogressguarddemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
    // 1. Check whether the IBinder object exists locally
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if(((iin ! =null) && (iin instanceof com.project.horselai.bindprogressguarddemo.IMyAidlInterface))) {
        // If it is local communication, local communication will take place later
        return ((com.project.horselai.bindprogressguarddemo.IMyAidlInterface) iin);
    }
    // 2. Otherwise, use this object for remote communication later
    return new com.project.horselai.bindprogressguarddemo.IMyAidlInterface.Stub.Proxy(obj);
}
Copy the code

Looking at the Stub#onTranscat method, each parameter does the following

  • Code:Identifies the action to be performed as a slaveFIRST_CALL_TRANSACTIONtoLAST_CALL_TRANSACTIONBetween the numbers.
  • Data: transcat()Data sent by the caller.
  • Reply:Used totranscat()The caller writes the reply data.
  • Flags:If it is0, stands for an ordinaryRPCIf it isFLAG_ONEWAYIt means oneone-wayThe type ofRPC.
  • Return:returntrueThe request is successful and returnsfalseYou do not understand the transaction code (code).

Based on the theory above, we already know that onTransact() in process A is called by process B to call back data remotely. Here are two symbolic methods to explain what happens in onTransact() :

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    java.lang.String descriptor = DESCRIPTOR;
    switch (code) {
        // For remote write requests
        case TRANSACTION_sendMessage: {
            data.enforceInterface(descriptor);
            java.lang.String _arg0;
            // 1. Read request data from process A's remote request packet
            _arg0 = data.readString();
            // 2. Execute sendMessage in process B to write data from process A
            this.sendMessage(_arg0);
            reply.writeNoException();
            return true;
        }
        // For remote read requests
        case TRANSACTION_getProcessId: {
            data.enforceInterface(descriptor);
            Run getProcessId() in process B to read the data to be used as response data
            int _result = this.getProcessId();
            // 2. Write the read response data to process A's reply
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
        // ...
        default: {
            return super.onTransact(code, data, reply, flags); }}}Copy the code

SendMessage (_arg0) and this.getProcessid () above, such as why this.sendMessage(_arg0) is executed in TRANSACTION_sendMessage, Isn’t that a loop? No. Why? Because TRANSACTION_sendMessage determines the method type code from process A, and after parsing the request parameter data from process A, process B calls its own sendMessage(_arg0) method to save the data to its own memory. SendMessage (_arg0) has its own implementation, as follows in process B:

IMyAidlInterface.Stub myAidlInterface = new IMyAidlInterface.Stub() {
    @Override
    public void sendMessage(String msg) throws RemoteException {
        Log.i(TAG, "sendMessage: " + msg);
    } 

    @Override
    public int getProcessId(a) throws RemoteException {
        returnProcess.myPid(); }};Copy the code

It makes a lot of sense here.

Proxy#sendMessage and Proxy#getProcessId are used to explain how client process A sends requests to remote server process B:

@Override
public void sendMessage(java.lang.String msg) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain(); // Request parameters
    android.os.Parcel _reply = android.os.Parcel.obtain(); // Response data
    try {
        // 1. Encapsulate the remote request parameters
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeString(msg);
        // 2. Remote requests are executed with Binder and the final response data is wrapped in _reply
        mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply, 0);
        // 3. No data needs to be returned
        _reply.readException();
    } finally{ _reply.recycle(); _data.recycle(); }}@Override
public int getProcessId(a) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain(); // Request parameters
    android.os.Parcel _reply = android.os.Parcel.obtain(); // Response data
    int _result;
    try {
        // 1. If no parameter is specified, only identifiers are written
        _data.writeInterfaceToken(DESCRIPTOR);
        // 2. Remote requests are executed with Binder and the final response data is wrapped in _reply
        mRemote.transact(Stub.TRANSACTION_getProcessId, _data, _reply, 0);
        // 3. Read response data after transact blocking ends
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}
Copy the code

As you can see, _reply has always been the same, created by process A and sent to process B, which writes the processed response data to _reply and finally calls back to process A via the onTranscat method, thus completing an RPC.

In general, the execution process of the whole process is as follows:

Four,MessengerUse and source code analysis

1. Use

Use the following in a Service process:

public class MessengerRemoteService extends Service {

    private static final String TAG = "MessengerRemoteService";
    private Messenger mMessenger;
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
 
    @Override
    public void onCreate(a) {
        super.onCreate();

        // 3. Use Messenger to communicate between processes
        mMessenger = new Messenger(new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.i(TAG, "handleMessage: " + msg);
                Log.i(TAG, "handleMessage data: " + msg.getData().get("msg"));
                return true; }})); }}Copy the code

Then establish the service connection in the Activity as follows:

// for Messenger
private Messenger mMessenger;
ServiceConnection mServiceConnection3 = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "onServiceConnected3: ");
        mMessenger = new Messenger(service);
        btnBindRemote.setEnabled(false);
        mIsBond = true;
        Toast.makeText(MainActivity.this."service bond 3!", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i(TAG, "onServiceDisconnected 3: ");
        mIsBond = false;
        btnBindRemote.setEnabled(true); }};Copy the code

After binding, the following information is sent:

if(mMessenger ! =null){
    Message message = new Message();
    Bundle bundle = new Bundle();
    bundle.putString("msg"."message clicked from Main ..");
    message.what = 122;
    message.setData(bundle);
    try {
        mMessenger.send( message);
    } catch(RemoteException e) { e.printStackTrace(); }}Copy the code

The following output is displayed:

Based on the above use, you can see that the communication for Messenger is one-way. If you want two-way communication, you need to create A Messenger and Handler on process A as the client, and then send the response message in process B.

To enable two-way communication, we can modify the above code as follows, where Messenger in MessengerRemoteService can be modified as follows:

mMessenger = new Messenger(new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        Log.i(TAG, "handleMessage: " + msg);
        Log.i(TAG, "handleMessage data: " + msg.getData().get("msg"));

        Message message = Message.obtain();
        message.replyTo = mMessenger;
        Bundle bundle = new Bundle();
        bundle.putString("msg"."MSG from MessengerRemoteService..");
        message.setData(bundle);
        message.what = 124;
        try {
            // Notice here
            msg.replyTo.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return true; }}));Copy the code

Note msg.replyto.send (message) above, where msg.replyto is a Messenger that sends this message. You can change it like this in your Activity:

/ / onCreate
mClientMessenger = new Messenger(mHandler);

// ...
Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == 2) {
            Toast.makeText(MainActivity.this."" + msg.obj, Toast.LENGTH_SHORT).show();
            return true;
        }else if (msg.what == 124){
            Toast.makeText(MainActivity.this,   msg.getData().getString("msg"), Toast.LENGTH_SHORT).show();
            Log.i(TAG, "handleMessage: " + msg.getData().getString("msg"));
            Log.i(TAG, "handleMessage: ");
            return true;
        }
        textView.setText(String.valueOf(msg.obj));
        return true; }});Copy the code

Finally, when the Activity receives a message, it pops up the received message, as shown below:

The whole two-way communication process is as follows:

2. Implementation principle of Messenger

The Messenger layer is simply wrapped with Binder, specifically AIDL, so it doesn’t affect the life cycle of a process, although the connection is broken when a process is destroyed.

Here is a brief look at some of its source code:

public final class Messenger implements Parcelable {
    private final IMessenger mTarget;

    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
    // ...
}
Copy the code

IMessenger is an AIDL interface, as follows:

package android.os; 
import android.os.Message; 
/ * *@hide* /
oneway interface IMessenger {
    void send(in Message msg);
} 
Copy the code

It’s pretty easy to understand this stuff with the previous knowledge.

Five, the summary

This paper mainly describes two ways of Android interprocess communication: AIDL and Socket. The Socket method is not described and analyzed too much in this paper, because the use of Socket communication is relatively basic, and its implementation process is relatively easy to understand, so it is briefly covered. See AndroidIPCDemo for detailed implementation source code. This paper focuses on the operation mechanism of Binder from the perspective of AIDL source code, and briefly introduces the use and implementation of Messenger.

OK, the level is limited, welcome rational correction.