Messenger进程间通信及其原理

来源:互联网 发布:c语言打印等腰杨辉三角 编辑:程序博客网 时间:2024/06/16 15:32

前言

之前分析Android消息机制的源码时,曾遇到过replyTo、IMessenger等属性字段,当时只是说这些字段用于进程间通信,并未作深入分析。今天这篇文字就来演示一下使用Messenger如何进行进程间通信并分析其源码实现。

Messenger进程间通信的流程

Messenger顾名思义,即信使,那么它的作用就是满足不同进程两边的通信需要了。通常我们会写AIDL来实现进程间通信,其实简单的IPC可以用Messenger来实现,需要知道的是Messenger也是基于AIDL的,只不过Messenger帮我们做了封装而已,其进程间通信框架是这样的:

这里写图片描述

如上图,假设两个进程分别为Client Process和Server Process,首先Server端需要将自己这边的Messenger引用传给Client,然后Client使用Server端传过来的Messenger来发消息给Server端,这样就实现了一个单向通信。同理,如果想要实现双向通信,则需要Client端也发送一个自己的Messenger到Server端,那么Server端也就可以利用该Messenger向Client发消息了。虽然Messenger是基于AIDL的,但它们最底层都是基于Binder的。

Messenger进程间双向通信示例

创建一个Service模拟Server进程

一般的进程间通信多是在两个App之间,但一个App中也可以有多进程,这个很常见,如应用中的推送服务一般位于单独的进程。当然我们可以把这个Service创建到另一个App中,但为了方便测试,这里只是将该Service注册为另一个进程,但还是在同一个应用中。

该Service的实现很简单,如下:

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();        }    }

上述代码虽然简单,但有几点需要注意:

  1. 为什么Service中要开一个工作线程?因为Service作为四大组件之一,它是运行在主线程的,所以不能执行耗时操作,一旦进程间交互是耗时操作,那么Service所在进程就会阻塞,而Client端进程则不会阻塞。
  2. 该Service中创建了一个Messenger对象,并在onBind中返回了IBinder对象,这里是进程间通信的关键,在后面会详细分析。
  3. 该Service的子线程中创建了一个Handler,并关联给Messenger,用于进程间通信的消息处理。Handler消息处理跟我们平时用的一样,但有一点提一下,子线程是没有默认Looper的,因此需要自己创建并启动,否则子线程的Handler无法收到Message。
  4. Server端收到消息后,Toast一下“hello server”并显示Cient传过来的两个整数值。如果Client端也将自己的Messenger传过来了,则向Client端回复消息,将两个整数之和返回。

另外该Service在AndroidManifest.xml中的注册如下:

<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>

核心一句为android:process=":remote",将该Service置于另一个进程之中,从而可以在同一个App中模拟进程间通信。

创建一个Activity模拟Client进程

该Activity默认就是该App所在进程了,具体实现如下:

/** * 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);    }}

代码逻辑也很简单,界面有3个按钮,操作如下:

  1. 先启动Server端的Service,暂且叫做启动远程Service
  2. 绑定远程Service
  3. Client向Servcie端发送消息,并接收返回的消息

需要注意的有如下几点:

  1. 绑定远程Service后,Client端才拿到了Server端的Messenger引用。
  2. Client端的Messenger需要关联自己的Handler,用来处理从Server端收到的消息。这里也需要注意,理论上如果Server端与Client端交互也是耗时的话,也需要开子线程,这个例子中由于只是显示下消息,直接放在UI线程了。
  3. 如果需要双向通信,Client端需要通过Message的replyTo参数将自己的Messenger发到Server端。
  4. Android 5.0+要求绑定Service时必须使用显式Intent,可以通过设置包名的方式来解决,注意我是在同一个App中开的两个进程,因此包名相同,但如果远程Service位于另一个App,则应该填写其所在App的包名。
  5. Client端收到回复消息后,Toast“Hello client”及两个整数之和。

示例效果演示

以上示例的进程间通信效果演示如下:

这里写图片描述

Messenger进程间通信原理分析

关于Service的启动、绑定不必多说,先从Client端通过绑定远程Service获取Server端的Messenger入手,代码如下:

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;    }};

接着来看mRemoteMessenger = new Messenger(service);的源码实现:

/** * 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);}

注意到该构造方法的参数IBinder,就是远程Service中onBind返回的,具体代码如下:

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

再来看这一句代码:

mTarget = IMessenger.Stub.asInterface(target);

mTarget是IMessenger对象,看起来越来越像AIDL的写法了,其实不能说像,本来就是AIDL。于是猜想源码必定中有一个名为IMessenger.aidl的文件,它应该定义了发送消息的相关接口。果然在源码目录 “/frameworks/base/core/java/android/os/”下找到了IMessenger.aidl文件,其内容如下:

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

因此可知Messenger只是帮我们省去了写AIDL的工作而已,底层还是AIDL。

再来看Messenger是如何发送消息的,即Messenger的send方法:

/** * 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);}

通过注释可知,Messenger会将消息发送到其关联的Handler,且Handler不存在时会报异常,这就是我们无论是创建客户端还是服务端Messenger时都为其创建了一个Handler的原因。

另外上述示例中为了简便,只是在进程间传递了基本类型的值,其实类似单进程的消息机制,也可以传递Bundle数据,但注意需要序列化,具体说明可参考Message源码的基本字段支撑。

0 0
原创粉丝点击