近一個半月因為工作變動的緣故,忙著交接工作和複習面試。沒有多少時間來寫博客,連一周三次的健身都有幾個星期沒練了,好多同事問我是胖了還是壯了(我迅速就岔開話題了,機智 boy)。上周離職,這周主要在處理一些私事、做些入職準備工作、看點書之類的,下周入職 YY(上周才知道原來大神羅昇陽也在 YY)。好啦,說了這麼多,要開始進入 Messenger 的正題了。
前言看這篇文章前,需要對 Android 的進程間通訊方式有所了解,不然可能會雲裡霧裡。
從使用 Messenger 說起Android 上實現 IPC (進程間通訊)的方式有好幾種,其中有一種就是使用 AIDL 方式實現,對使用 AIDL 不了解的童鞋可以看下方的官方文檔(需要梯子)。
https://developer.android.com/guide/components/aidl.html
對於使用 AIDL 方式通訊,其關鍵就在於創建 aidl 文件,系統會自動為 aidl 文件生成相應的 Java 類,其關鍵實現在於生成的 Java 類中。
系統提供了一個更方便我們進行 IPC 的類 —— Messenger,先來看看如何使用 Messenger(熟悉的童鞋完全可以跳過這一部分)。
private Messenger mSender; private Messenger mReceiver = new Messenger(new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Bundle data = msg.getData(); if (data != null) { String response = data.getString("body"); Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show(); } } });
public class IPCService extends Service { private Messenger messenger = new Messenger(new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } Message response = Message.obtain(); Bundle data = new Bundle(); data.putString("body", "response"); response.setData(data); try { msg.replyTo.send(response); } catch (RemoteException e) { e.printStackTrace(); } } }); @Override public IBinder onBind(Intent intent) { return messenger.getBinder(); }}
Intent intent = new Intent(this, IPCService.class); startService(intent); bindService(intent, conn, Context.BIND_AUTO_CREATE); private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mSender = new Messenger(service); } @Override public void onServiceDisconnected(ComponentName name) { } };
if (mSender != null) { Message message = Message.obtain(); message.replyTo = mReceiver; try { mSender.send(message); } catch (RemoteException e) { e.printStackTrace(); } }
至此,在綁定服務進程初始化 Sender 後,即可以做多進程間的交互工作了。使用 Messenger 來實現多進程的交互相比我們用 aidl 來要方便得多,但是 Messenger 的內部也是採用 aidl 實現的,只不過為了方便開發者調用而進行一些封裝,使得開發者們可以忽略 aidl 的實現細節。簡單的了解了 Messenger 的基本使用後,下面我們就來看看 Messenger 的原始碼,了解一些內部的實現細節。
Messenger 原始碼通讀Messenger 類位於 android.os 包下,代碼量不是很多,所以看起來難度不大,只有如下這麼幾個方法。
在上面的示例代碼中可以看到,客戶端進程有 Sender 和 Receiver 兩個 Messenger,如果不需要實現服務進程回調客戶端進程,那麼 Receiver 完全可以不要。當需要服務進程回調客戶端進程時,則需要傳入 Receiver 了。由於需要在進程間傳遞 Messenger 對象,那麼 Messenger 類就必須要繼承 Serializable 或者 Parcelable 接口。按照 Android 系統一向的風格,都是偏向於推薦繼承 Parcelable 來實現。
所以,上面看到的 describeContents、writeToParcel 方法和 CREATOR 對象實際上繼承 Parcelable 的實現。不明白的童鞋可以參照 Parcelable 的官方文檔(需要梯子)
https://developer.android.com/reference/android/os/Parcelable.html
而 equals 和 hashCode 方法自然不用多說啦,大家熟悉得很。而 writeMessengerOrNullToParcel 和 readMessengerOrNullFromParcel 這對靜態方法主要是實現 Messenger 在 Parcel 中的讀寫操作的,實現比較簡單,大家參見下面代碼就可以理解了。
當然,Messenger 除了繼承 Parcelable 外,還需要聲明一個同名的 Messenger.aidl 文件,可以在 framework 層源碼下 android.os 包中找到 Messenger.aidl 文件,對於寫過 aidl 的童鞋,肯定不陌生了。
排除掉上面提到的方法,剩下的主要是 Messenger 的兩個構造方法以及 getBinder 和 send 方法。客戶端調用服務進程方法時,是通過 Messenger 中的 send 方法,所以我們先直接看 send 方法中的內部實現
其內部是調用了 mTarget 的 send 方法,那麼 mTarget 又是何物呢?
從上面可以看到 mTarget 是一個 IMessenger 實例,作為 Messenger 唯一的成員變量。 初始化 mTarget 是在 public Messenger(Handler target) 構造函數中,利用 Handler 的 getIMessenger 方法來獲取一個 IMessenger 的實例。
從上圖可以看到 getIMessenger 前面是沒有修飾符的,這樣控制了該方法的作用域僅限於 android.os 包內給其他類使用,我們日常開發是無法使用該方法的,所以在 API 文檔中也沒有提供相應接口文檔。在倒數第二行中可以看到 new MessengerImpl() 並在最後 return 給調用者。所以實際上, mTarget 具體實現是在 MessengerImpl 中。
MessengerImpl 實際上是 Handler 的一個私有內部類,它繼承了 IMessenger.Stub 並實現 send 方法。用過 aidl 的童鞋 IMessenger.Stub 的身影想必就明白了,實際上 IMessenger 就是系統提供的 IMessenger.aidl 文件,而 IMessenger.Stub 就是由 IMessenger.aidl 生成的類。 IMessenger.aidl 在 framework 層代碼的 android.os 包中可以找到,而關於 IMessenger 的 Java 實現,則可以看下面的連結。
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/os/IMessenger.java/
MessengerImpl 的 send 實現相對也比較簡單,只有兩行代碼
public void send(Message msg) { msg.sendingUid = Binder.getCallingUid(); Handler.this.sendMessage(msg); }
首先是將發起調用的客戶端進程的 Linux Uid 存儲在我們傳入的 Message 對象中,服務進程收到 Message 可以通過 msg.sendingUid 得知發起調用的進程的 Linux Uid。接著通過 Handler 的 sendMessage 方法發送給服務進程,這意味著 Messenger 與服務進程間的操作是串行的,因此,在有並行需求的場景下 Messenger 就不適用了。
了解完了 send 方法後,最後就只剩下 getBinder 了,其內部實現也簡單。
參照前面的示例代碼,這裡主要還是在 Service 的 onBind 方法中返回 Binder 對象給客戶端調用,實現同樣是 IMessenger 的 Java 實現中。
至此,基本上看完了整個 Messenger 的內部代碼,從上面的分析上看,內部實現確實非常簡單,基於 aidl 的基礎上做的封裝實現,又對開發者屏蔽了底層 aidl 的實現細節。當然個人認為有兩點不足之處:
遠程調用的阻塞與非阻塞Android 系統跨進程通訊的底層實現都是通過 Binder 實現,正常情況下客戶端進程發起一個遠程方法調用的流程大致如下:
1. 客戶端線程發起調用;
2. 客戶端線程將遠程調用操作交給 Binder 線程池,並阻塞等待返回遠程方法執行完畢返回;
這也就意味著,我們不能在客戶端進程的 UI 線程中發起遠程方法調用,不然如果遠程方法執行了耗時操作,客戶端的 UI 線程將會被阻塞,從而造成 ANR 的問題存在。讀者可以自行嘗試自定義 aidl 並發起一個耗時的遠程方法調用進行驗證。但是,如果你使用系統提供的 Messenger ,則不會出現這樣的問題,無論你的遠程方法執行多麼耗時,客戶端 Messenger 發起調用後會繼續執行接下來的代碼,並不會進行阻塞等待。這裡讓我百思不得其解,為什麼呢?前面我們可以看到 Messenger 的 send 方法實現是在 MessengerImpl 中
並且,發送 Messeage 的操作是利用主線程的 Handler ,並沒有其他的異步操作,為何執行的過程中不阻塞?這點我也完全沒有想明白,最後折騰半天無果向任玉剛老師求助才得到答案。原來,默認情況下發起的遠程方法調用都是阻塞式的,但也可以是非阻塞式的。 Messenger 就是採用非阻塞的方式通訊,其關鍵就在於 IMessenger.aidl 的實現
相比平常自定義的 aidl,多了 oneway 的關鍵字,聲明和不聲明 oneway 關鍵字的在於生成 Java 類中一個參數
不聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 0;聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 android.os.IBinder.FLAG_ONEWAY 。
查看 API 文檔即可以看到 FLAG_ONEWAY 的作用就是讓客戶端能夠非阻塞的調用遠程方法,至此真相大白,如果我們自定義的 aidl 也想實現非阻塞的調用,只需聲明 oneway 關鍵字即可。
總結因為平時並不常用到 oneway,加上文檔提及的很少,唯一有描述的就是下面這段。這次看了 Messenger 的代碼才知道有這麼回事,也是漲姿勢了。
對我的博文感興趣,可以關注我的簡書喲:
http://www.jianshu.com/users/ec95b5891948/latest_articles
了解最新移動開發相關信息和技術,請關注mobilehub公眾微信號(ID: mobilehub)。