Realization of interprocess communication with Android messenger and its principle

Time:2021-7-25

preface

When analyzing the source code of Android message mechanism before, I encountered attribute fields such as replyto and imessenger. At that time, I just said that these fields were used for inter process communication without in-depth analysis. Today’s article will demonstrate how to use messenger for inter process communication and analyze its source code implementation.

Process of messenger interprocess communication

Messenger, as its name implies, is used to meet the communication needs of both sides of different processes. We usually write Aidl to realize interprocess communication. In fact, simple IPC can be realized by messenger. We need to know that messenger is also based on Aidl, but messenger helps us encapsulate it. Its interprocess communication framework is as follows:

As shown in the figure above, suppose that the two processes are client process and server process respectively. First, the server side needs to pass its messenger reference to the client, and then the client uses the messenger sent from the server side to send messages to the server side, so as to realize a one-way communication. Similarly, if you want to realize two-way communication, the client side needs to send its own messenger to the server side, and the server side can use the messenger to send messages to the client. Although messenger is based on Aidl, the bottom layer of messenger is based on binder.

Example of two-way communication between messenger processes

Create a service impersonation server process

Generally, interprocess communication is mostly between two apps, but there can also be multiple processes in an app. This is very common. For example, the push service in an application is generally located in a separate process. Of course, we can create this service into another app, but for the convenience of testing, we only register the service as another process, but it is still in the same application.

The implementation of the service is very simple, as follows:


public class RemoteService extends Service {
    private WorkThread mWorkThread = new WorkThread();
    private Messenger mMessenger;

    @Override
    public void onCreate() {
        super.onCreate();
        mWorkThread.start();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mWorkThread.quit();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }

    private void prepareMessenger() {
        mMessenger = new Messenger(mWorkThread.mHandler);
    }

    private class WorkThread extends Thread {
        Handler mHandler;

        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case MessageConstant.CLIENT_TO_SERVER:
                            Toast.makeText(RemoteService.this, "Hello Server:" + msg.arg1 + "," + msg.arg2, Toast.LENGTH_SHORT).show();
                            if (msg.replyTo != null) {
                                try {
                                    msg.replyTo.send(Message.obtain(null, MessageConstant.SERVER_TO_CLIENT, 0, msg.arg1 + msg.arg2));
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }
                            }
                            break;
                        default:
                            break;
                    }

                }
            };
            prepareMessenger();
            Looper.loop();
        }

        public void quit() {
            mHandler.getLooper().quit();
        }
    }

Although the above code is simple, there are several points to note:

1. Why open a worker thread in the service? As one of the four components, service runs on the main thread, so it cannot perform time-consuming operations. Once the interaction between processes is time-consuming, the process where the service is located will block, while the client process will not block.
2. A messenger object is created in the service and an ibinder object is returned in onbind. This is the key to inter process communication, which will be analyzed in detail later.
3. A handler is created in the sub thread of the service and associated with messenger for message processing of inter process communication. The handler message processing is the same as we usually use, but it is mentioned that the sub thread does not have a default looper, so it needs to be created and started by itself, otherwise the handler of the sub thread cannot receive the message.
4. After the server receives the message, toast will click “Hello server” and display the two integer values sent by cient. If the client also sends its own Messenger, it will reply to the client and return the sum of the two integers.

In addition, the service is registered in androidmanifest.xml as follows:


<service
    android:name=".messenger.RemoteService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote">
    <intent-filter>
        <action android:name="com.aspook.remote.ACTION_BIND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

The core sentence isandroid:process=”:remote”, put the service in another process, so that inter process communication can be simulated in the same app.

Create an activity to simulate the client process

The activity is the process of the app by default. The specific implementation is as follows:


/**
 * demo for IPC by Messenger
 */
public class MessengerActivity extends AppCompatActivity {

    private Button btn_start;
    private Button btn_bind;
    private Button btn_send;
    private boolean mBound = false;
    private Messenger mRemoteMessenger = null;

    private ServiceConnection mRemoteConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mRemoteMessenger = new Messenger(service);
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteMessenger = null;
            mBound = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);

        findViews();
        setListeners();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mRemoteConnection);
    }

    public void findViews() {
        btn_start = (Button) findViewById(R.id.btn_start);
        btn_bind = (Button) findViewById(R.id.btn_bind);
        btn_send = (Button) findViewById(R.id.btn_send);
    }

    public void setListeners() {
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // start Remote Service first
                Intent intent = new Intent(MessengerActivity.this, RemoteService.class);
                startService(intent);
                btn_start.setEnabled(false);
            }
        });

        btn_bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // bind the Remote Service, if the Remote service run in another App, you should run the App and start the service first
                try {
                    bindRemoteService();
                    btn_bind.setEnabled(false);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        btn_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mBound) {
                    Handler mClientHandler = new Handler() {
                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            switch (msg.what) {
                                case MessageConstant.SERVER_TO_CLIENT:
                                    Toast.makeText(MessengerActivity.this, "Hello Client:" + msg.arg2, Toast.LENGTH_SHORT).show();

                                    break;
                                default:
                                    break;
                            }
                        }
                    };

                    try {
                        Message msg = Message.obtain(null, MessageConstant.CLIENT_TO_SERVER, 66, 88);
                        // Messenger of client sended to server is used for sending message to client
                        msg.replyTo = new Messenger(mClientHandler);
                        mRemoteMessenger.send(msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(MessengerActivity.this, "Service not bind", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    /**
     * bind service
     */
    public void bindRemoteService() {
        // Method one
        Intent intent = new Intent("com.aspook.remote.ACTION_BIND");// 5.0+ need explicit intent
        intent.setPackage("com.aspook.androidnotes"); // the package name of Remote Service
        bindService(intent, mRemoteConnection, BIND_AUTO_CREATE);
    }
}

The code logic is also very simple. There are three buttons on the interface. The operations are as follows:

1. Start the server-side service first, which is temporarily called starting the remote service
2. Bind remote service
3. The client sends a message to the servcie and receives the returned message

The following points should be noted:

1. After binding the remote service, the client side gets the messenger reference of the server side.
2. Messenger on the client side needs to be associated with its own handler to process messages received from the server side. It should also be noted here that theoretically, if the interaction between the server side and the client side is also time-consuming, it is also necessary to open sub threads. In this example, because the message is only displayed, it is directly placed in the UI thread.
3. If two-way communication is required, the client side needs to send its own messenger to the server side through the replyto parameter of message.
4. Android 5.0 + requires explicit intent when binding a service, which can be solved by setting the package name. Note that I have two processes running in the same app, so the package name is the same, but if the remote service is located in another app, the package name of the app where it is located should be filled in.
5. After the client receives the reply message, toast “Hello client” and the sum of two integers.

Example effect demonstration

The inter process communication effect of the above example is demonstrated as follows:

Analysis of messenger interprocess communication principle

Needless to say about service startup and binding, start with the client side obtaining the server side messenger by binding the remote service. The code is as follows:


private ServiceConnection mRemoteConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mRemoteMessenger = new Messenger(service);
        mBound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mRemoteMessenger = null;
        mBound = false;
    }
};

Next, let’s look at mremotemessage = new messenger (service); Source code implementation:


/**
 * Create a Messenger from a raw IBinder, which had previously been
 * retrieved with {@link #getBinder}.
 * 
 * @param target The IBinder this Messenger should communicate with.
 */
public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

Note that the parameter ibinder of the construction method is returned by onbind in the remote service. The specific code is as follows:


@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
}

Let’s look at this Code:


mTarget = IMessenger.Stub.asInterface(target);

Mtarget is an imessenger object. It looks more and more like Aidl. In fact, it can’t be said to be like Aidl. So I guess there must be a file named imessenger.aidl in the source code, which should define the relevant interface for sending messages. Sure enough, the imessenger.aidl file was found in the source directory “/ frameworks / base / core / Java / Android / OS /”, and its contents are as follows:


package android.os;
import android.os.Message;

/** @hide */
oneway interface IMessenger {
    void send(in Message msg);
}

Therefore, messenger just saves us the work of writing Aidl, and the bottom layer is still Aidl.

Let’s look at how messenger sends messages, that is, Messenger’s send method:


/**
 * Send a Message to this Messenger's Handler.
 * 
 * @param message The Message to send.  Usually retrieved through
 * {@link Message#obtain() Message.obtain()}.
 * 
 * @throws RemoteException Throws DeadObjectException if the target
 * Handler no longer exists.
 */
public void send(Message message) throws RemoteException {
    mTarget.send(message);
}

According to the comments, Messenger will send messages to its associated handler, and an exception will be reported when the handler does not exist, which is why we created a handler for both client and server messenger.

In addition, in the above example, for simplicity, only basic types of values are passed between processes. In fact, similar to the single process message mechanism, bundle data can also be passed. However, please note that serialization is required. For details, please refer to the basic field support of the message source code.

The above is the whole content of this article. I hope it will be helpful to your study, and I hope you can support developpaer.