android系统原理解读安卓基础知识程序员

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

2016-04-16  本文已影响2016人  丶蓝天白云梦

一、前言

在前几篇文章中,笔者讲述了利用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实例化的,这个用于从服务端发送数据到客户端。

上一篇下一篇

猜你喜欢

热点阅读