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,如下所示: