1. 程式人生 > >Android 中的 IPC 方式(二) Messenger

Android 中的 IPC 方式(二) Messenger

Messenger 可以翻譯為信使,顧名思義,通過它可以在不同程序中傳遞 Message 物件,在 Message 中加入我們需要傳遞的資料,就可以輕鬆地實現資料的程序間傳遞了。Messenger 是一種輕量的 IPC 方案,它的底層實現是 AIDL,下面是 Messenger 的兩個構造,從構造方法的實現上我們可以明顯看出 AIDL 的痕跡,不管是 Messenger 還是 Stub.asInterface,這種使用方法都表明它的底層是 AIDL。

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

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

Messenger 的使用方法很簡單,它對 AIDL 做了封裝,是的我們可以更簡單的進行程序間通訊。同時,由於它一次處理一個請求,因此在服務端我們不用考慮執行緒同步的問題,這是因為服務端中不存在併發執行的情形。實現一個 Messenger 有如下幾個步驟,分為服務端和客戶端:

(1)服務端程序

首先,我們需要在服務端建立一個 Service,來處理客戶端的連線請求,同時建立一個 Handler 並通過它來建立一個 Messenger 物件,然後在 Service 的 onBind 中返回這個 Messenger 物件底層的 Binder 即可。

(2)客戶端程序

客戶端程序中,首先要繫結服務端的 Service,繫結成功後用服務端返回的 IBinder 物件建立一個 Messenger,通過 Messenger 就可以向服務端傳送訊息了,發訊息型別為 Message 物件。如果需要服務端能迴應客戶端,就和服務端一樣,我們還需要建立一個 IBinder 並建立一個新的 Messenger,並把這個 Messenger 物件通過 Message 的 replyTo 引數傳遞給服務端,服務端通過 replyTo 引數就可以迴應客戶端。首先我們看一個簡單的例子,在這個例子中服務端無法迴應客戶端。

首先看服務端的程式碼,可以看到 MessengerHandler 用來處理客戶端傳送的訊息,並從訊息中取出客戶端發來的文字資訊。而 mMessenger 是一個 Messenger 物件,它和 MessengerHandler 相關聯,並在 onBind 中返回它的 Binder 物件,可以看出,這裡 Messenger 的作用是將客戶端傳送來的訊息傳遞給 MessengerHandler 處理。

public class MessengerService extends Service {

    public final String TAG = this.getClass().getName();

    private class MessengerHandler extends Handler {
       @Override
       public void handleMessage(Message msg) {
           if(msg.what == 1) {
                Log.i(TAG, "receive msg from client:" + msg.getData().getString("msg"));
           }
       }
   }

   private Messenger messenger = new Messenger(new MessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }

}

當然,不要忘記在 AndroidManifest 中註冊:

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

接下來我們看客戶端的程式碼,客戶端的實現也比較簡單,肯定需要繫結遠端服務的 MessengerService,繫結成功後,根據服務端傳送的 Binder 物件建立 Messenger 物件並根據這個物件向服務端傳送訊息,下面程式碼正在 Bundle 中向服務端傳送了一句話,在上面的服務端會列印這句話。

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            messenger = new Messenger(service);
            Message msg = new Message();
            Bundle bundle = new Bundle();
            bundle.putString("msg", "Hello,this is client.");
            msg.setData(bundle);
            msg.what = 1;
            try {
                messenger.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);
        bindService(new Intent(this, MessengerService.class), connection, Context.BIND_AUTO_CREATE);
    }

執行之後,我們來看下 log 日誌:

09-12 14:33:33.771 23040-23040/com.demo.text.demotext:remote I/com.demo.text.demotext.service.MessengerService: receive msg from client:Hello,this is client.

通過上面的例子可以看出,在 Messenger 中進行資料傳遞必須將資料放入 Message 中, 而 Messenger 和 Message 都實現了 Parcelable 介面,因此可以跨程序傳輸。簡單來說,Message 所支援的資料型別就是 Messenger 所支援的傳輸型別。實際上,通過 Messenger 來傳輸 Message,Message 中使用的載體只有 what、arg1、arg2、Bundle 以及 replyTo。Message 中的另一個欄位 object 在同一個程序中是很實用的,但是在程序間通訊的時候,在 Android 2.2以前 object 欄位不支援程序間通訊,即使 2.2 以後,也僅僅是系統提供的實現了 Parcelable 介面的物件才能通過它來傳輸,這就意味著我們自定義的 Parcelable 物件是無法通過 object 欄位來傳輸的。

上面的例子演示瞭如何在服務端接收客戶端發來的資訊,但是有時候我們還需要能夠迴應客戶端,下面介紹如何實現這種效果,還是採用上面的例子,稍微做一下修改,每當客戶端發來一條資訊,服務端就會自動恢復一套。

首先看服務端的修改,服務端只需要修改 MessengerHandler,當收到訊息後,會立即回覆一條給客戶端:

    private class MessengerHandler extends Handler {
       @Override
       public void handleMessage(Message msg) {
           if(msg.what == 1) {
                Log.i(TAG, "receive msg from client:" + msg.getData().getString("msg"));
                Messenger client = msg.replyTo;
                Message replyMsg = new Message();
                replyMsg.what = 1;
               Bundle bundle = new Bundle();
               bundle.putString("reply", "嗯,你的訊息我已經收到,馬上回復你。");
               replyMsg.setData(bundle);
               try {
                   client.send(replyMsg);
               } catch (RemoteException e) {
                   e.printStackTrace();
               }
           }
       }
   }

接著再看客戶端的修改,為了接收服務端的回覆,客戶端也需要準備一個接收訊息的 Messenger 的 Handler,如下所示

 private Messenger replyMessenger = new Messenger(new MessaengerHandler());

    private class MessaengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == 1) {
                Log.i(MainActivity.this.getClass().getName(), "receiver msg from service:" + msg.getData().getString("reply"));
            }
        }
    }

除了上述修改,還有關鍵的一點,當客戶端傳送訊息的時候,需要把接收服務端回覆的 Messenger 通過 Message 的 replyTo 引數傳遞給伺服器,如下所示:

      Messenger messenger = new Messenger(service);
            Message msg = new Message();
            Bundle bundle = new Bundle();
            bundle.putString("msg", "Hello,this is client.");
            msg.setData(bundle);
            msg.what = 1;
            try {
                msg.replyTo = replyMessenger;
                messenger.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

通過上述修改,我們在執行程式,檢視 log 日誌:

/com.demo.text.demotext:remote I/com.demo.text.demotext.service.MessengerService: receive msg from client:Hello,this is client.
/com.demo.text.demotext I/com.demo.text.demotext.MainActivity: receiver msg from service:嗯,你的訊息我已經收到,馬上回復你。

到這裡,我們已經把採用 Messenger 實現程序間通訊的方式都介紹完了,下面給出一張 Messenger 的工作原理圖,方便更好地理解 Messenger,如下所示: