1. 程式人生 > >Android IPC機制(五):詳解Bundle與“信使”——Messenger

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(Handler target) {
        mTarget = target.getIMessenger(); // 2 
    }
public Messenger(IBinder target) {        // 1
        mTarget = IMessenger.Stub.asInterface(target);
    }



①號構造方法,傳遞一個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;<span style="white-space:pre">		</span>//  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;

    private Messenger replyMessenger = new Messenger(new MessengerHandler());   // 1 為客戶端例項化一個Messenger,用於處理從服務端傳遞過來的資料
    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例項化的,

這個用於從服務端傳送資料到客戶端。