本節書摘來自異步社群《深入解析android 5.0系統》一書中的第6章,第6.5節程序間的消息傳遞,作者 劉超,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視
6.5 程序間的消息傳遞
深入解析android 5.0系統
android的消息可以在程序之間傳遞。程序間消息傳遞是建立在binder通信基礎之上的。binder本身用來在程序間傳遞資訊已經足夠了,這裡介紹的程序間消息傳遞方法隻是讓應用在設計上更加便利,并不是架構上大的改進。
我們知道,隻要有了binder的引用對象就可以調用其功能。android中如果希望向另一個程序的handler發送消息,一定要通過某個binder對象來代理完成。在handler類中,方法getimessage()會建立一個binder對象,代碼如下:
getimessenger()中建立的對象的類型是messengerimpl,它是一個binder的服務類,從imessenger.stub類派生。messengerimpl中的send()方法的作用就是給handler發消息。代碼如下:
是以,調用某個handler對象的getimessenger()方法将得到能給這個handler對象發送消息的binder對象。但是要跨程序發送消息,還需要把這個binder對象傳遞到調用程序中。
6.5.1 了解messenger類
messenger類實際上是對imessenger對象的包裝,它裡面包含了一個handler對象關聯的messengerimpl對象的引用。messenger類的構造方法如下:
使用messenger類的好處是隐藏了通信中使用binder的細節,讓整個過程看起來就像在發送一個本地消息一樣簡單。messenger類的send()方法用來發送消息,它也是通過binder對象在傳遞消息,代碼如下:
注意 send()方法并不是在本地調用的,通常要把messager對象傳遞到另一個程序後再使用。messenger類實作了parcelable接口,是以,它能很友善地傳遞到另一個程序。
如果隻需要在兩個程序間進行簡單的消息往來,上面介紹的知識已經夠用了。為了友善應用使用,android還提供了asyncchannel類來完成建立雙向通信的過程。
6.5.2 建立通信通道——asyncchannel類的作用
asyncchannel類用來建立兩個handler之間的通信通道。這兩個handler可以在一個程序中,也可以在兩個程序中。但是通信雙方的地位并不是對等的,一方要充當service并響應asyncchannel類中定義的消息。另一方則充當client的角色,主動去連接配接對方。
使用asyncchannel類首先要确定通信雙方使用“半連接配接模式”還是“全連接配接模式”。所謂的“半連接配接模式”是指連接配接建立後,隻能用戶端主動給服務端發送消息,服務端可以在收到用戶端的消息後利用消息中附帶的messenger對象來給用戶端“回複”消息,但是不能主動給用戶端發送消息。“全連接配接模式”則是雙方都能主動向對方發送消息。顯然,“全連接配接模式”比“半連接配接模式”占用更多的系統資源。
1.半連接配接模式
asyncchannel類中提供很多個連接配接方法,用戶端需要根據情況使用。但是在連接配接前,還是要先得到服務端的messenger對象。前面介紹過,jave層中binder的傳遞有兩種方式,一種方式是通過已經建立好的binder通道來傳遞binder對象。另一種方式是通過元件service來獲得service中包含的binder對象。asyncchannel類同時支援這兩種方式。
如果用戶端和服務已經建立了binder通道,那麼服務的messenger對象就可以通過它傳遞到用戶端,這樣我們就可以使用asyncchannel類的connect()方法來建立兩者之間的聯系了。
connect()方法的參數srccontext是用戶端的上下文,srchandler是用戶端的handler對象,dstmessenger是從服務端傳遞來的messenger對象。
asyncchannel還為通信雙方定義了非常簡單的握手協定,connect()方法會調用replyhalf connected()方法來發送cmd_channel_half_connected消息給用戶端的srchandler。為什麼消息是發給用戶端中的handler對象而不是發到伺服器呢?其實這個簡單的握手協定主要是為全連接配接模式準備的,半連接配接隻要得到了對方的messenger就可以通信了,但是全連接配接模式下必須先確定通信雙方都準備好了才能開始通信過程,是以需要一個簡單的握手協定。在半連接配接情況下的srchandler對象不需要去處理cmd_channel_half_connected消息,調用完connect()方法後就可以開始發送消息了。
如果用戶端和服務端之間沒有現成的binder通道,可以通過啟動元件service的方式來建立一個binder通道,當然,在服務端實作的service中包含的binder對象必須是messengerimpl對象。如果不願意重新寫一個service,android中提供一個類asyncservice,服務端可以直接使用它來建立一個service。在這種情況下,用戶端需要使用asyncchannel類的另一個connect()方法來建立連接配接。
connect()方法會根據傳入的包名和類名去啟動service,啟動的時間可能比較長,是以,connect()方法中使用了線程去啟動service。
new thread(ca).start();
這個線程的執行方法如下:
在onserviceconnected()方法中把傳遞回來的binder引用對象包裝成了messenger對象并儲存在mdstmessenger變量中,如果需要給服務端發送消息,調用mdstmessenger對象的send()方法就可以了。onserviceconnected()方法中也調用了replyhalfconnected()方法來給用戶端的handler對象發送cmd_channel_half_connected消息,在目前這種情形下這條消息就有意義了,用戶端的handler對象收到消息才可以和服務端通信。
2.全連接配接模式
全連接配接模式是建立在半連接配接模式基礎上的,當用戶端的handler對象收到消息cmd_ channel_half_connected以後,如果希望建立全連接配接,需要再向服務端發送cmd_channel_ full_connection消息,同時在消息中附上用戶端的messenger對象。服務端收到消息後,還需要給用戶端回複cmd_channel_fully_connected消息,如果服務端同意建立全連接配接,會将消息的第一個參數msg.arg1的值設定為0,否則設定為非0值。圖6.3是全連接配接模式的消息互動圖。
下面以android中的wifiservice和用戶端wifimanager為例來了解全連接配接模式的建立過程。
先看用戶端的實作,下面是類wifimanager中定義的handler:
wifimanager收到了消息cmd_channel_half_connected後,調用sendmessage()方法給wifiservice發送了一條cmd_channel_full_connection的消息。sendmessage()方法會把本端的messenger對象附到消息中。
現在看看服務端是如何處理收到的消息的。下面是wifiservice類中定義的handler:
服務端收到cmd_channel_full_connection消息後,建立了自己的asyncchannel對象,然後調用它的connect()方法和用戶端的messenger進行綁定。其實,asyncchannel對象也隻能向一個方向發送消息,所謂的雙向通信就是雙方都有自己的asyncchannel來發送消息。前面介紹了,調用connect()方法會給本端的handler對象發送cmd_channel_half_connected消息,是以,服務端也對這條消息進行了處理,處理的方式是把用戶端的messenger對象儲存到了成員變量mtrafficpoller數組中。對于服務端而言,它連接配接的用戶端可能是多個,是以,它要用數組儲存所有用戶端的messenger。這樣整個過程就完畢了。這裡注意,wifiservice并沒有向wifimanager回複cmd_channel_fully_connected消息。實際上這個回複的确有點多餘,wfiiservice并沒有使用它。
從上面的分析不難看出,asyncchannel的作用就是實作了建立雙向binder連接配接的過程,友善應用使用。不過asyncchannel類也提供了新的功能:同步消息發送。
3.發送同步消息
asyncchannel的sendmessagesynchronously()方法可以用來發送同步消息。sendmessagesynchronously()方法在發送完消息後會挂起線程進入等待狀态,收到回複的消息後再恢複線程的運作。asyncchannel中定義了一個嵌入類syncmessenger來完成同步消息的發送。syncmessenger類中定義的同步消息發送方法sendmessagesynchronously()的代碼如下:
在發往另一端的消息中,msg.replyto設定成了syncmessenger對象,這樣,回複消息将不會送到asynchannel的srchandler對象,而是被syncmessenger對象收到。同時,發送完消息後,調用wait()方法挂起線程。
服務端發送的回複消息将在syncmessenger中定義的synchandler中處理。
上面的代碼中把回複消息儲存到變量mresultmsg中後調用notify()方法解鎖線程。線程恢複運作後将傳回收到的消息,這樣一次同步消息的發送就結束了。