Android Messenger實(shí)現(xiàn)進(jìn)程間通信及其原理
前言
之前分析Android消息機(jī)制的源碼時(shí),曾遇到過replyTo、IMessenger等屬性字段,當(dāng)時(shí)只是說這些字段用于進(jìn)程間通信,并未作深入分析。今天這篇文字就來演示一下使用Messenger如何進(jìn)行進(jìn)程間通信并分析其源碼實(shí)現(xiàn)。
Messenger進(jìn)程間通信的流程
Messenger顧名思義,即信使,那么它的作用就是滿足不同進(jìn)程兩邊的通信需要了。通常我們會(huì)寫AIDL來實(shí)現(xiàn)進(jìn)程間通信,其實(shí)簡單的IPC可以用Messenger來實(shí)現(xiàn),需要知道的是Messenger也是基于AIDL的,只不過Messenger幫我們做了封裝而已,其進(jìn)程間通信框架是這樣的:
如上圖,假設(shè)兩個(gè)進(jìn)程分別為Client Process和Server Process,首先Server端需要將自己這邊的Messenger引用傳給Client,然后Client使用Server端傳過來的Messenger來發(fā)消息給Server端,這樣就實(shí)現(xiàn)了一個(gè)單向通信。同理,如果想要實(shí)現(xiàn)雙向通信,則需要Client端也發(fā)送一個(gè)自己的Messenger到Server端,那么Server端也就可以利用該Messenger向Client發(fā)消息了。雖然Messenger是基于AIDL的,但它們最底層都是基于Binder的。
Messenger進(jìn)程間雙向通信示例
創(chuàng)建一個(gè)Service模擬Server進(jìn)程
一般的進(jìn)程間通信多是在兩個(gè)App之間,但一個(gè)App中也可以有多進(jìn)程,這個(gè)很常見,如應(yīng)用中的推送服務(wù)一般位于單獨(dú)的進(jìn)程。當(dāng)然我們可以把這個(gè)Service創(chuàng)建到另一個(gè)App中,但為了方便測試,這里只是將該Service注冊(cè)為另一個(gè)進(jìn)程,但還是在同一個(gè)應(yīng)用中。
該Service的實(shí)現(xiàn)很簡單,如下:
public class RemoteService extends Service { private WorkThread mWorkThread = new WorkThread(); private Messenger mMessenger; @Override public void onCreate() { super.onCreate(); mWorkThread.start(); } @Override public void onDestroy() { super.onDestroy(); mWorkThread.quit(); } @Nullable @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } private void prepareMessenger() { mMessenger = new Messenger(mWorkThread.mHandler); } private class WorkThread extends Thread { Handler mHandler; @Override public void run() { Looper.prepare(); mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case MessageConstant.CLIENT_TO_SERVER: Toast.makeText(RemoteService.this, "Hello Server:" + msg.arg1 + "," + msg.arg2, Toast.LENGTH_SHORT).show(); if (msg.replyTo != null) { try { msg.replyTo.send(Message.obtain(null, MessageConstant.SERVER_TO_CLIENT, 0, msg.arg1 + msg.arg2)); } catch (RemoteException e) { e.printStackTrace(); } } break; default: break; } } }; prepareMessenger(); Looper.loop(); } public void quit() { mHandler.getLooper().quit(); } }
上述代碼雖然簡單,但有幾點(diǎn)需要注意:
1、為什么Service中要開一個(gè)工作線程?因?yàn)镾ervice作為四大組件之一,它是運(yùn)行在主線程的,所以不能執(zhí)行耗時(shí)操作,一旦進(jìn)程間交互是耗時(shí)操作,那么Service所在進(jìn)程就會(huì)阻塞,而Client端進(jìn)程則不會(huì)阻塞。
2、該Service中創(chuàng)建了一個(gè)Messenger對(duì)象,并在onBind中返回了IBinder對(duì)象,這里是進(jìn)程間通信的關(guān)鍵,在后面會(huì)詳細(xì)分析。
3、該Service的子線程中創(chuàng)建了一個(gè)Handler,并關(guān)聯(lián)給Messenger,用于進(jìn)程間通信的消息處理。Handler消息處理跟我們平時(shí)用的一樣,但有一點(diǎn)提一下,子線程是沒有默認(rèn)Looper的,因此需要自己創(chuàng)建并啟動(dòng),否則子線程的Handler無法收到Message。
4、Server端收到消息后,Toast一下“hello server”并顯示Cient傳過來的兩個(gè)整數(shù)值。如果Client端也將自己的Messenger傳過來了,則向Client端回復(fù)消息,將兩個(gè)整數(shù)之和返回。
另外該Service在AndroidManifest.xml中的注冊(cè)如下:
<service android:name=".messenger.RemoteService" android:enabled="true" android:exported="true" android:process=":remote"> <intent-filter> <action android:name="com.aspook.remote.ACTION_BIND" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
核心一句為android:process=":remote",將該Service置于另一個(gè)進(jìn)程之中,從而可以在同一個(gè)App中模擬進(jìn)程間通信。
創(chuàng)建一個(gè)Activity模擬Client進(jìn)程
該Activity默認(rèn)就是該App所在進(jìn)程了,具體實(shí)現(xiàn)如下:
/** * demo for IPC by Messenger */ public class MessengerActivity extends AppCompatActivity { private Button btn_start; private Button btn_bind; private Button btn_send; private boolean mBound = false; private Messenger mRemoteMessenger = null; private ServiceConnection mRemoteConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mRemoteMessenger = new Messenger(service); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mRemoteMessenger = null; mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); findViews(); setListeners(); } @Override protected void onDestroy() { super.onDestroy(); unbindService(mRemoteConnection); } public void findViews() { btn_start = (Button) findViewById(R.id.btn_start); btn_bind = (Button) findViewById(R.id.btn_bind); btn_send = (Button) findViewById(R.id.btn_send); } public void setListeners() { btn_start.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // start Remote Service first Intent intent = new Intent(MessengerActivity.this, RemoteService.class); startService(intent); btn_start.setEnabled(false); } }); btn_bind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // bind the Remote Service, if the Remote service run in another App, you should run the App and start the service first try { bindRemoteService(); btn_bind.setEnabled(false); } catch (Exception e) { e.printStackTrace(); } } }); btn_send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mBound) { Handler mClientHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case MessageConstant.SERVER_TO_CLIENT: Toast.makeText(MessengerActivity.this, "Hello Client:" + msg.arg2, Toast.LENGTH_SHORT).show(); break; default: break; } } }; try { Message msg = Message.obtain(null, MessageConstant.CLIENT_TO_SERVER, 66, 88); // Messenger of client sended to server is used for sending message to client msg.replyTo = new Messenger(mClientHandler); mRemoteMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } else { Toast.makeText(MessengerActivity.this, "Service not bind", Toast.LENGTH_SHORT).show(); } } }); } /** * bind service */ public void bindRemoteService() { // Method one Intent intent = new Intent("com.aspook.remote.ACTION_BIND");// 5.0+ need explicit intent intent.setPackage("com.aspook.androidnotes"); // the package name of Remote Service bindService(intent, mRemoteConnection, BIND_AUTO_CREATE); } }
代碼邏輯也很簡單,界面有3個(gè)按鈕,操作如下:
1、先啟動(dòng)Server端的Service,暫且叫做啟動(dòng)遠(yuǎn)程Service
2、綁定遠(yuǎn)程Service
3、Client向Servcie端發(fā)送消息,并接收返回的消息
需要注意的有如下幾點(diǎn):
1、綁定遠(yuǎn)程Service后,Client端才拿到了Server端的Messenger引用。
2、Client端的Messenger需要關(guān)聯(lián)自己的Handler,用來處理從Server端收到的消息。這里也需要注意,理論上如果Server端與Client端交互也是耗時(shí)的話,也需要開子線程,這個(gè)例子中由于只是顯示下消息,直接放在UI線程了。
3、如果需要雙向通信,Client端需要通過Message的replyTo參數(shù)將自己的Messenger發(fā)到Server端。
4、Android 5.0+要求綁定Service時(shí)必須使用顯式Intent,可以通過設(shè)置包名的方式來解決,注意我是在同一個(gè)App中開的兩個(gè)進(jìn)程,因此包名相同,但如果遠(yuǎn)程Service位于另一個(gè)App,則應(yīng)該填寫其所在App的包名。
5、Client端收到回復(fù)消息后,Toast“Hello client”及兩個(gè)整數(shù)之和。
示例效果演示
以上示例的進(jìn)程間通信效果演示如下:
Messenger進(jìn)程間通信原理分析
關(guān)于Service的啟動(dòng)、綁定不必多說,先從Client端通過綁定遠(yuǎn)程Service獲取Server端的Messenger入手,代碼如下:
private ServiceConnection mRemoteConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mRemoteMessenger = new Messenger(service); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mRemoteMessenger = null; mBound = false; } };
接著來看mRemoteMessenger = new Messenger(service);的源碼實(shí)現(xiàn):
/** * Create a Messenger from a raw IBinder, which had previously been * retrieved with {@link #getBinder}. * * @param target The IBinder this Messenger should communicate with. */ public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
注意到該構(gòu)造方法的參數(shù)IBinder,就是遠(yuǎn)程Service中onBind返回的,具體代碼如下:
@Nullable @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); }
再來看這一句代碼:
mTarget = IMessenger.Stub.asInterface(target);
mTarget是IMessenger對(duì)象,看起來越來越像AIDL的寫法了,其實(shí)不能說像,本來就是AIDL。于是猜想源碼必定中有一個(gè)名為IMessenger.aidl的文件,它應(yīng)該定義了發(fā)送消息的相關(guān)接口。果然在源碼目錄 “/frameworks/base/core/java/android/os/”下找到了IMessenger.aidl文件,其內(nèi)容如下:
package android.os; import android.os.Message; /** @hide */ oneway interface IMessenger { void send(in Message msg); }
因此可知Messenger只是幫我們省去了寫AIDL的工作而已,底層還是AIDL。
再來看Messenger是如何發(fā)送消息的,即Messenger的send方法:
/** * Send a Message to this Messenger's Handler. * * @param message The Message to send. Usually retrieved through * {@link Message#obtain() Message.obtain()}. * * @throws RemoteException Throws DeadObjectException if the target * Handler no longer exists. */ public void send(Message message) throws RemoteException { mTarget.send(message); }
通過注釋可知,Messenger會(huì)將消息發(fā)送到其關(guān)聯(lián)的Handler,且Handler不存在時(shí)會(huì)報(bào)異常,這就是我們無論是創(chuàng)建客戶端還是服務(wù)端Messenger時(shí)都為其創(chuàng)建了一個(gè)Handler的原因。
另外上述示例中為了簡便,只是在進(jìn)程間傳遞了基本類型的值,其實(shí)類似單進(jìn)程的消息機(jī)制,也可以傳遞Bundle數(shù)據(jù),但注意需要序列化,具體說明可參考Message源碼的基本字段支撐。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)加載狀態(tài)視圖切換效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)加載狀態(tài)視圖切換效果的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android中使用socket使底層和framework通信的實(shí)現(xiàn)方法
native和framework的通信是通過jni,但是這一般只是framework調(diào)用native,native如果有消息要怎樣通知上層 呢?android中GSP模塊提供一種解決思路,但是實(shí)現(xiàn)有些復(fù)雜,這里介紹一種使用socket通信的方法可以使native和framework自由通信,感興趣的朋友一起看看吧2016-11-11保持Android Service在手機(jī)休眠后繼續(xù)運(yùn)行的方法
下面小編就為大家分享一篇保持Android Service在手機(jī)休眠后繼續(xù)運(yùn)行的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-03-03Android編程實(shí)現(xiàn)控件不同狀態(tài)文字顯示不同顏色的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)控件不同狀態(tài)文字顯示不同顏色的方法,涉及Android針對(duì)控件布局文件屬性設(shè)置及狀態(tài)判定等相關(guān)技巧,需要的朋友可以參考下2016-02-02Android ListView中headerview的動(dòng)態(tài)顯示和隱藏的實(shí)現(xiàn)方法
這篇文章主要介紹了Android ListView中headerview的動(dòng)態(tài)顯示和隱藏的實(shí)現(xiàn)方法的相關(guān)資料,這里提供兩種方法幫助實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-08-08Flutter使用AnimatedSwitcher實(shí)現(xiàn)場景切換動(dòng)畫
在應(yīng)用中,我們經(jīng)常會(huì)遇到切換組件的場景。本文將利用Flutter中提供的AnimatedSwitcher這一動(dòng)畫組件來實(shí)現(xiàn)頁面內(nèi)的場景切換,需要的可參考一下2022-03-03