一、前言
在前幾篇文章中,筆者講述了利用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接口,三個同時用于進程間通信,那么這三者有什么聯系嗎?
其實根據每一個類的構造方法以及主要函數,我們便可以知道這三者的聯系了?,F在我們把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實例化的,這個用于從服務端發送數據到客戶端。