Advanced Android - lightweight cross process Message delivery tool Messenger details

Posted by dawho9 on Sat, 19 Feb 2022 23:55:15 +0100

introduction

As Android developers, I believe we must be very familiar with the Message mechanism, and we must be in charge of using Handler to process messages. If you are allowed to use the simplest way to realize interprocess communication, a considerable number of beginners may think of using AIDL to realize it themselves. It is true that the idea is right, but there is a simpler mechanism for you to use.

1, Messenger overview

Messenger is a lightweight IPC inter process communication method based on Message delivery (by creating a messenger pointing to the Handler in one process and passing the messenger to another process). Of course, its essence is the encapsulation of Binder (also implemented through AIDL). Messenger allows us to simply use Handler to deliver messages directly between processes. The cross process is implemented through Binder (AIDL), while the Message is sent through Handler#sendMessage method, and the processing is handled by Handler#handleMessage; Of course, in addition to the Handler, it can also be customized and related IBinder interfaces. In short, the cross process capability of messenger is provided by the associated object during construction.

2, Messenger source code analysis

Messenger implements the Parcelable interface, which means that it can be passed across processes. Meanwhile, holding the IMessenger interface reference (a Binder object) means that it can be used across processes after getting the Binder object. Messenger just wraps the IMessenger interface and passes it across processes through Binder. The real core capability provider is the implementation class of IMessenger - Android os. Handler. MessengerImpl.

package android.os;

/**
 * Reference to a Handler, which others can use to send messages to it.
 * This allows for the implementation of message-based communication across
 * processes, by creating a Messenger pointing to a Handler in one process,
 * and handing that Messenger to another process.
 */
public final class Messenger implements Parcelable {
    private final IMessenger mTarget;

    /**
     * Create a new Messenger pointing to the given Handler.  Any Message
     * objects sent through this Messenger will appear in the Handler as if
     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had been called directly.
     * 
     * @param target The Handler that will receive sent messages.
     */
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

    /**
     * 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);
    }
    
    /**
     * Send a Message to this Messenger's Handler.
     * 
     * @param message The Message to send.  Usually retrieved through
     * {@link Message#obtain() Message.obtain()}.
     */
    public void send(Message message) throws RemoteException {
        mTarget.send(message);
    }
    
    /**
     * Retrieve the IBinder that this Messenger is using to communicate with
     * its associated Handler.
     * @return Returns the IBinder backing this Messenger.
     */
    public IBinder getBinder() {
        return mTarget.asBinder();
    }
    
    public boolean equals(Object otherObj) {
        if (otherObj == null) {
            return false;
        }
        try {
            return mTarget.asBinder().equals(((Messenger)otherObj)
                    .mTarget.asBinder());
        } catch (ClassCastException e) {
        }
        return false;
    }

    public int hashCode() {
        return mTarget.asBinder().hashCode();
    }
    
    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeStrongBinder(mTarget.asBinder());
    }

    public static final Parcelable.Creator<Messenger> CREATOR
            = new Parcelable.Creator<Messenger>() {
        public Messenger createFromParcel(Parcel in) {
            IBinder target = in.readStrongBinder();
            return target != null ? new Messenger(target) : null;
        }

        public Messenger[] newArray(int size) {
            return new Messenger[size];
        }
    };

    public static void writeMessengerOrNullToParcel(Messenger messenger,
            Parcel out) {
        out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder()
                : null);
    }

    public static Messenger readMessengerOrNullFromParcel(Parcel in) {
        IBinder b = in.readStrongBinder();
        return b != null ? new Messenger(b) : null;
    }
}

1. IMessenger interface

IMessenger is automatically generated through AIDL. Generally, in the native Android system, the I prefix is the implementation class corresponding to the AIDL interface. Corresponding messenger AIDL:

package android.os;

parcelable Messenger;

And imessenger Aidl defines a method send(in Message msg) whose input parameter is Message

package android.os;

import android.os.Message;

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

IMessenger.aidl implementation class corresponding to AIDL:

public interface IMessenger extends android.os.IInterface {
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements
            android.os.IMessenger {
        private static final java.lang.String DESCRIPTOR = "android.os.IMessenger";

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

        public static android.os.IMessenger asInterface(...}

        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 {...}

        private static class Proxy implements android.os.IMessenger {...}

    public void send(android.os.Message msg)
            throws android.os.RemoteException;
}

IMessenger is a Binder interface, which only provides one method - send, which is used to send messages across processes.

2. Main methods of Messenger

2.1,Messenger(Handler target)

 public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

When constructing Messenger through Handler, the incoming Handler#getIMessenger() method is called to get the singleton constructed Messenger impl instance and initialize mTarget.

//Handler#getIMessenger()    
final IMessenger getIMessenger() {
        synchronized (mQueue) {
            if (mMessenger != null) {
                return mMessenger;
            }
            mMessenger = new MessengerImpl();
            return mMessenger;
        }
    }

Keep chasing

 private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }

The essence is to complete communication through Handler#sendMessage.

2.2,Messenger(IBinder target)

When constructing Messenger through IBinder object, it is to "convert" the incoming IBinder object into a MessengerImpl instance and initialize the mTarget member variable.

It is not simply a direct forced conversion, but retrieval first. If it has been created, it will directly return the corresponding proxy object of IBinder. Otherwise, it will create the corresponding proxy object and then return. For details, please follow Binder series articles.

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);    
}

2.3,send(Message message)

The essence of the send method is to call the Handler#sendMessage method, which also explains why we need to create a Handler on both the server and the client, because we need to process the received message in the Handler.

public void send(Message message) throws RemoteException {
        mTarget.send(message);//MessengerImpl#send
    }

3, Use of Messenger

Messenger can communicate across processes based on Binder. For convenience, I simply call one process the server process and the other the client process

1. First, define a Messenger object on the server

Messager.replyTo refers to the Messenger of the client, and Messenger holds an IBinder object (Messenger impl) of the client. The server uses this IBinder object to communicate with the client.

  • Create a Handler
  • Using Handler initialization to build Messenger
package com.crazymo.messenger.rawmessenger;

import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;

public class MessengerService extends Service {
    public final static String TAG = "MessengerIPC";
    public final static String KEY_NAME = "Name";
    public final static String KEY_RESP = "Response";
    public final static int MSG_WHAT_HELLO = 100;
    public final static int MSG_WHAT_RESP = 1001;
    /**
     * A serialized object for cross process, wrapped with IMessenger AIDL interface
     */
    private static final Messenger messenger = new Messenger(new MessengerServerHandler());

    private static class MessengerServerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            doHandleMessage(msg);
        }
    }

    /**
     * Handle messages sent by other processes
     * @param msg
     */
    private static void doHandleMessage(Message msg) {
        if (msg != null) {
            String ret = "hello ";
            //Receive and process messages from clients
            if (msg.what == MSG_WHAT_HELLO) {
                Log.e(TAG, "receive msg from client=" + msg.getData().getString(KEY_NAME));
                try {
                    Thread.sleep(1_000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ret += msg.getData().getString(KEY_NAME);
                //Encapsulate the processing result into a Message and return it to the client
                Message reply = Message.obtain(null, MSG_WHAT_RESP);
                Bundle bundle = new Bundle();
                bundle.putString(KEY_RESP, ret);
                reply.setData(bundle);
                try {
                    //msg.replyTo @ android.os.Messenger type, Messenger Replyto points to the messenger of the client, and messenger holds a Binder object (messenger impl) of the client. The server uses this Binder object to communicate with the client.
                    if (msg.replyTo != null) {
                        msg.replyTo.send(reply);
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        } else {
            Log.e(TAG, "handle client empty msg");
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        //Return the IBinder object of Messenger. This callback will be triggered when bindService is executed, and you can get the IBinder object of the server
        return messenger.getBinder();
    }
}

Then, the logic of message processing is implemented in the incoming Handler#handleMessage method. So far, a remote Service is implemented.

2. Client using Messenger

  • Define a Handler to send messages
  • Initialize Messenger object
package com.crazymo.messenger;

import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import com.crazymo.messenger.aidl.MyMessengerService;
import com.crazymo.messenger.rawmessenger.MessengerService;

public class MainActivity extends AppCompatActivity {
    private final static String TAG="MainActivity";
    /***********************1,Messenger Mode****************************/
    private Messenger mServer;
    private ServiceConnection connMessenger =new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mServer=new Messenger(service);//Initialize the returned IBinder object to Messenger
            Log.e(MessengerService.TAG, "MessengerService Connected!");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private final Handler handlerClient =new Handler(){
        @SuppressLint("HandlerLeak")
        @Override
        public void handleMessage(Message msg) {
            if(msg!=null && msg.what== MessengerService.MSG_WHAT_RESP){
                String resp=msg.getData().getString(MessengerService.KEY_RESP);
                Log.e(MessengerService.TAG, "resp from server="+resp);
            }
        }
    };

    //In order to receive the reply from the server, the client also needs to prepare a Messenger and Handler to receive messages
    private final Messenger clientMessenger=new Messenger(handlerClient);

    private void bindMessengerService() {
        Intent intent=new Intent(this,MessengerService.class);
        bindService(intent, connMessenger, Context.BIND_AUTO_CREATE);

    }

    public void sendByMessenger(View view) {
        Message msg=Message.obtain(null,MessengerService.MSG_WHAT_HELLO);
        Bundle data=new Bundle();
        data.putString(MessengerService.KEY_NAME,"CrazyMo_");
        msg.setData(data);
        //When the Client sends a Message, specify the person you want to reply to, and set the Messenger object of the Client process to the Message
        msg.replyTo=clientMessenger;
        try {
            mServer.send(msg);//Cross process delivery
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /***********************2,MyMessenger AIDL Mode****************************/
    private IMyMessenger myInterface;
    private ServiceConnection connAidl = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myInterface = IMyMessenger.Stub.asInterface(service);
            Log.i(TAG, "MyMessenger connect Service success");
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "connect Service fail");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindMessengerService();
        bindMyMessengerService();
    }

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

    public void sendByMyMessenger(View view) {
        try {
            String ret=myInterface.send("hello server my MyMessenger");
            Log.i(TAG, "myInterface.send Results="+ret);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void bindMyMessengerService() {
        Intent intent=new Intent(this, MyMessengerService.class);
        bindService(intent, connAidl, Context.BIND_AUTO_CREATE);
    }
}

3. Traditional AIDL implementation VS Messenger

// IMyMessenger.aidl
package com.crazymo.messenger;

interface IMyMessenger {

    String send( String aString);
}

public class MyMessengerService  extends Service {

    private static final String TAG="MyMessengerService";
    private MyMessengerBinder mBinder=new MyMessengerBinder();
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private static class MyMessengerBinder extends IMyMessenger.Stub{
        @Override
        public String send(String aString) throws RemoteException {
            String ret="reply to client"+aString;
            Log.e(TAG,"received str from c:"+aString);
            return ret;
        }
    }
}

As like as two peas, you will find that using traditional AIDL and Messenger in the form of original service is very similar. The difference is only when initialization, when onServiceConnected method callback initializes, the way to construct remote Binder objects is different, and the rest is basically the same.

Topics: Android binder aidl