Android IPC机制(五):详解Bundle与“信使”——Messenger

一、前言

在前几篇文章中,笔者讲述了利用AIDL方式进行进程间通讯,并对Binder及Binder连接池的使用方法和原理进行了分析。其实,进程间通讯还存在多种方式,AIDL方式只是其中之一,只不过由于AIDL方式的功能比较全面,所以AIDL方式用得也比较多。除了AIDL方式之外,还有Bundle、Messenger、ContenProvider、Socket、文件共享等多种方式。各种方式都有不同的适用场景,本文介绍Bundle和Messenger方式,这两个一般结合在一起使用。

二、什么是Bundle?

先看官方文档对其的描述:A mapping from String values to various Parcelable types.
  可以看出,它和Map类型有异曲同工之妙,同时实现了Parcelable接口,那么显然,它是支持进程间通讯的。所以,Bundle可以看做是一个特殊的Map类型,它支持进程间通讯,保存了特定的数据。
  以下是Bundle的几个常用方法:①putXxx(String key,Xxx value):Xxx表示一系列的数据类型,比如String,int,float,Parcelable,Serializable等类型,以键-值对形式保存数据。②getXxx(String key):根据key值获取Bundle中的数据。

三、“信使”——Messenger

Messenger是一种轻量级IPC方案,其底层实现原理就是AIDL,它对AIDL做了一次封装,所以使用方法会比AIDL简单,由于它的效率比较低,一次只能处理一次请求,所以不存在线程同步的问题。
  先看官方文档的描述: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.
  大概意思是说,首先Messenger要与一个Handler相关联,才允许以message为基础的会话进行跨进程通讯。通过创建一个messenger指向一个handler在同一个进程内,然后就可以在另一个进程处理这个messenger了。
  我们看Messenger类的两个构造方法:

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

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

①号构造方法,传递一个IBinder对象,然后,执行了asInterface(target)方法,这个方法简直就是AIDL方式里面讲到的方法有没有!再看②号方法,暂时看不出什么端倪,那么我们按住Ctrl,对着这个方法点鼠标左键
,直接追踪该方法的来源,这时跳转到了Handler.java源码:

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

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

聪明的读者肯定已经发现了,这两个方法正是上一章Binder连接池所说到的,甚至也使用了线程同步的懒汉式单例模式!所以,Messenger的底层是AIDL方式,底层实现方式甚至和AIDL使用方法一模一样!
   →我们接着来看Messenger的两个重要方法:
(1)getBinder():返回一个IBinder对象,一般在服务端的onBind方法调用这个方法,返回给客户端一个IBinder对象
(2)send(Message msg):发送一个message对象到messengerHandler。这里,我们传递的参数是一个Message对象,为了说明Message和Messenger的联系,我们接下来要说说Message。

四、“信封”——Message

如果说Messenger充当了信使的角色,那么Message就充当了一个信封的角色。同样地,先看官方文档的描述:Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object **contains two extra int fields and an extra object field **that allow you to not do allocations in many cases.While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.
  从官文的描述可知,该Message对象含有两个Int型的属性和一个object型的属性,然后为了得到Message的实例,最好调用Message.obtain()方法而不是直接通过构造器。我们来看看主要参数以及重要方法:
(1)public int arg1,public int arg2,public Object obj:一般这三个属性用于保存数据,其中Object对象用于保存一个对象,所以一般把Bundle对象
放进Object字段。
(2)public Messenger replyTo:这个属性一般用于服务端需要返回消息给客户端的时候用到,下面会说到。
(3)public int what:这个属性用于描述这个message,一般在实例化的时候会传递这个参数。
(4)obtain():这个方法提供了多个参数的重载方法,为了获得message实例。
(5)setData(Bundle data):设置obj的值。

五、Bundle、Messenger和Message之间的联系

上面说到了Bundle、Messenger、Message这三个类,三个都实现了Parcelable接口,三个同时用于进程间通信,那么这三者有什么联系吗?
  其实根据每一个类的构造方法以及主要函数,我们便可以知道这三者的联系了。现在我们把Messenger比喻为一个信使,信使的作用是派信;那么Message就比喻为信件、信封,即信使派的东西;那么Bundle是什么呢?Message里面保存了Bundle,那么bundle可以比喻为信纸,信纸上写满了各种我们要传递的信息。读到这里,读者应该明白了这三者在Messenger通讯方式内所扮演的角色了。简单来说:Messenger把装有Bundle的Message发送到别的进程。接下来,我们以一个实例来加深读者对Messenger通讯方式的理解。

六、实例

1、首先,先建立Person类,实现Serializable接口:

public class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2、服务端MessengerService类:

public class MessengerService extends Service {

    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){ 
                case MainActivity.CLIENT:
                    Log.d("cylog","得到了客户端发送的消息:"+
                     msg.getData().getSerializable("msg").toString()); 
                    Messenger client = msg.replyTo; // 1
                    Message replyMessage = Message.obtain(null,1);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply","服务端已经收到了消息啦!");
                    replyMessage.setData(bundle);
                    try {
                        client.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private final Messenger mMessenger = new Messenger(new MessengerHandler());
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

首先,声明了一个内部类,继承Handler,重写了handleMessage方法,当有Message消息传递过来的时候,该方法就会回调。然后判断msg.what值,选择触发条件, 接着按步骤从msg中取出bundle,从bundle中取出Serializable对象。
  我们再看①号方法,定义了一个名为client的Messenger对象,该对象从msg.replyTo中获得引用,从上面的分析我们知道,**msg.replyTo也是一个Messenger对象。
  此外,还应该在AndroidManifest.xml中指定如下:

<service android:name=".MessengerService"
            android:process=":remote"></service>

使service端运行在独立进程中。
  3、客户端MainActivity类:

public class MainActivity extends Activity {

    private Messenger mService;
    public final static int CLIENT = 0;

    // 1 为客户端实例化一个Messenger,用于处理从服务端传递过来的数据
    private Messenger replyMessenger = new Messenger(new MessengerHandler());   
    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    Log.d("cylog", "收到了来自服务端的信息:"+msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            Message msg = Message.obtain(null,CLIENT);
            Bundle bundle = new Bundle();
            Person person = new Person("chenyu",20);
            bundle.putSerializable("msg",person);
            msg.setData(bundle);
            msg.replyTo = replyMessenger;      // 2
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this,MessengerService.class);
        bindService(intent,conn, Context.BIND_AUTO_CREATE);
    }
}

客户端也比较简单,在onCreate方法中,进行绑定服务(注意:这种方式前面说过了,不应该在主线程进行IPC操作,因为这是耗时的,这里为了方便才写在主线程)。然后在onServiceConnected()中,利用返回的service创建了一个Messenger,然后执行一系列的数据打包、存放、发送操作。有一个要注意的地方是②号代码,这里把msg.replyTo赋值为replyMessenger,实际上,这里把客户端的Messenger传递了进去(具体看①号代码)。那么服务端从mgs.replyTo取出的就是客户端的Messenger。

七、总结

1、传递的数据必须是Bundle所支持的数据类型,如果是新的数据类型必须实现Parcelable接口或者Serializable接口
2、接受消息的一端必须要有一个处理消息的Handler,Handler通常作为参数用于实例化一个Messenger。
3、如果服务端需要返回数据给客户端,那么在客户端中,需要把客户端自己的Messenger传递进msg.replyTo,这样服务器才能从msg.replyTo中取出特定的Messenger,从而返回信息。
4、在一次完整的进程间通讯(包括客户端的收发和服务端的收发)中,使用了两个不同的Messenger,第一个Messenger是用服务端返回的IBinder对象进行实例化的,这个用于从客户端发送数据到服务端;第二个Messenger是Handler实例化的,这个用于从服务端发送数据到客户端。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容