Android之IPC机制
Android IPC簡介
任何一個操作系統都需要有相應的IPC機制,Linux上可以通過命名通道、共享內存、信號量等來進行進程間通信。Android系統不僅可以使用了Binder機制來實現IPC,還可以使用Socket實現任意兩個終端之間的通信。
IPC基礎概念介紹
(1)Serializable接口是Java中為對象提供標準的序列化和反序列化操作的接口,而Parcelable接口是Android提供的序列化方式的接口。
(2)serialVersionUId是一串long型數字,主要是用來輔助序列化和反序列化的,原則上序列化后的數據中的serialVersionUId只有和當前類的serialVersionUId相同才能夠正常地被反序列化。
serialVersionUId的詳細工作機制:序列化的時候系統會把當前類的serialVersionUId寫入序列化的文件中,當反序列化的時候系統會去檢測文件中的serialVersionUId,看它是否和當前類的serialVersionUId一致,如果一致就說明序列化的類的版本和當前類的版本是相同的,這個時候可以成功反序列化;否則說明版本不一致無法正常反序列化。一般來說,我們應該手動指定serialVersionUId的值。
1.靜態成員變量屬于類不屬于對象,所以不參與序列化過程;
2.聲明為transient的成員變量不參與序列化過程。
(3)Parcelable接口內部包裝了可序列化的數據,可以在Binder中自由傳輸,Parcelable主要用在內存序列化上,可以直接序列化的有Intent、Bundle、Bitmap以及List和Map等等.
(4)Binder是Android中的一個類,它實現了IBinder接口。從IPC角度看,Binder是Android中一種跨進程通信的方式;Binder還可以理解為虛擬的物理設備,它的設備驅動是/dev/binder;從Framework層角度看,Binder是ServiceManager連接各種Manager和相應的ManagerService的橋梁;從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這里的服務包括普通服務和基于AIDL的服務。
在Android開發中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及進程間通信,較為簡單;而Messenger的底層其實是AIDL,正是Binder的核心工作機制。
(5)aidl工具根據aidl文件自動生成的java接口的解析:首先,它聲明了幾個接口方法,同時還聲明了幾個整型的id用于標識這些方法,id用于標識在transact過程中客戶端所請求的到底是哪個方法;接著,它聲明了一個內部類Stub,這個Stub就是一個Binder類,當客戶端和服務端都位于同一個進程時,方法調用不會走跨進程的transact過程,而當兩者位于不同進程時,方法調用需要走transact過程,這個邏輯由Stub內部的代理類Proxy來完成。
所以,這個接口的核心就是它的內部類Stub和Stub內部的代理類Proxy。 下面分析其中的方法:
1.asInterface(android.os.IBinder obj):用于將服務端的Binder對象轉換成客戶端所需的AIDL接口類型的對象,這種轉換過程是區分進程的,如果客戶端和服務端是在同一個進程中,那么這個方法返回的是服務端的Stub對象本身,否則返回的是系統封裝的Stub.Proxy對象。
2.asBinder:返回當前Binder對象。
3.onTransact:這個方法運行在服務端中的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝后交由此方法來處理。
這個方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags)
服務端通過code可以知道客戶端請求的目標方法,接著從data中取出所需的參數,然后執行目標方法,執行完畢之后,將結果寫入到reply中。如果此方法返回false,說明客戶端的請求失敗,利用這個特性可以做權限驗證(即驗證是否有權限調用該服務)。
4.Proxy#[Method]:代理類中的接口方法,這些方法運行在客戶端,當客戶端遠程調用此方法時,它的內部實現是:首先創建該方法所需要的參數,然后把方法的參數信息寫入到_data中,接著調用transact方法來發起RPC請求,同時當前線程掛起;然后服務端的onTransact方法會被調用,直到RPC過程返回后,當前線程繼續執行,并從_reply中取出RPC過程的返回結果,最后返回_reply中的數據。
如果搞清楚了自動生成的接口文件的結構和作用之后,其實是可以不用通過AIDL而直接實現Binder的.
(6)Binder的兩個重要方法linkToDeath和unlinkToDeath
Binder運行在服務端,如果由于某種原因服務端異常終止了的話會導致客戶端的遠程調用失敗,所以Binder提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath方法可以給Binder設置一個死亡代理,當Binder死亡的時候客戶端就會收到通知,然后就可以重新發起連接請求從而恢復連接了。
如何給Binder設置死亡代理呢?
1.聲明一個DeathRecipient對象,DeathRecipient是一個接口,其內部只有一個方法bindeDied,實現這個方法就可以在Binder死亡的時候收到通知了。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {@Overridepublic void binderDied() {if (mRemoteBookManager == null) return;mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);mRemoteBookManager = null;// TODO:這里重新綁定遠程Service} };2.在客戶端綁定遠程服務成功之后,給binder設置死亡代理
mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);Android中的IPC方式
(1)使用Bundle:Bundle實現了Parcelable接口,Activity、Service和Receiver都支持在Intent中傳遞Bundle數據。
(2)使用文件共享:這種方式簡單,適合在對數據同步要求不高的進程之間進行通信,并且要妥善處理并發讀寫的問題。
SharedPreferences是一個特例,雖然它也是文件的一種,但是由于系統對它的讀寫有一定的緩存策略,即在內存中會有一份SharedPreferences文件的緩存,因此在多進程模式下,系統對它的讀寫就變得不可靠,當面對高并發讀寫訪問的時候,有很大幾率會丟失數據,因此,不建議在進程間通信中使用SharedPreferences。
(3)使用Messenger:Messenger是一種輕量級的IPC方案,它的底層實現就是AIDL。Messenger是以串行的方式處理請求的,即服務端只能一個個處理,不存在并發執行的情形,詳細的示例見原書。
(4)使用AIDL
大致流程:首先建一個Service和一個AIDL接口,接著創建一個類繼承自AIDL接口中的Stub類并實現Stub類中的抽象方法,在Service的onBind方法中返回這個類的對象,然后客戶端就可以綁定服務端Service,建立連接后就可以訪問遠程服務端的方法了。
1.AIDL支持的數據類型:基本數據類型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL;
2.某些類即使和AIDL文件在同一個包中也要顯式import進來;
3.AIDL中除了基本數據類,其他類型的參數都要標上方向:in、out或者inout;
4.AIDL接口中支持方法,不支持聲明靜態變量;
5.為了方便AIDL的開發,建議把所有和AIDL相關的類和文件全部放入同一個包中,這樣做的好處是,當客戶端是另一個應用的時候,可以直接把整個包復制到客戶端工程中。
6.RemoteCallbackList是系統專門提供的用于刪除跨進程Listener的接口。RemoteCallbackList是一個泛型,支持管理任意的AIDL接口,因為所有的AIDL接口都繼承自IInterface接口。
實現的具體步驟參考:Android:學習AIDL,這一篇文章就夠了(上) - lypeer的專欄 - 博客頻道 - CSDN.NET
(5)使用ContentProvider
1.ContentProvider主要以表格的形式來組織數據,并且可以包含多個表;
2.ContentProvider還支持文件數據,比如圖片、視頻等,系統提供的MediaStore就是文件類型的ContentProvider;
3.ContentProvider對底層的數據存儲方式沒有任何要求,可以是SQLite、文件,甚至是內存中的一個對象都行;
4.要觀察ContentProvider中的數據變化情況,可以通過ContentResolver的registerContentObserver方法來注冊觀察者;
(6)使用Socket
Socket是網絡通信中“套接字”的概念,分為流式套接字和用戶數據包套接字兩種,分別對應網絡的傳輸控制層的TCP和UDP協議
Binder連接池
(1)當項目規模很大的時候,創建很多個Service是不對的做法,因為service是系統資源,太多的service會使得應用看起來很重,所以最好是將所有的AIDL放在同一個Service中去管理。整個工作機制是:每個業務模塊創建自己的AIDL接口并實現此接口,這個時候不同業務模塊之間是不能有耦合的,所有實現細節我們要單獨開來,然后向服務端提供自己的唯一標識和其對應的Binder對象;對于服務端來說,只需要一個Service,服務端提供一個queryBinder接口,這個接口能夠根據業務模塊的特征來返回相應的Binder對象給它們,不同的業務模塊拿到所需的Binder對象后就可以進行遠程方法調用了。
Binder連接池的主要作用就是將每個業務模塊的Binder請求統一轉發到遠程Service去執行,從而避免了重復創建Service的過程。
(2)作者實現的Binder連接池BinderPool的實現源碼,建議在AIDL開發工作中引入BinderPool機制。
選用合適的IPC方式
Android之Binder詳解
Binder框架定義了四個角色:Server,Client,ServiceManager(以后簡稱SMgr)以及Binder驅動。其中Server,Client,SMgr運行于用戶空間,驅動運行于內核空間。這四個角色的關系和互聯網類似:Server是服務器,Client是客戶終端,SMgr是域名服務器(DNS),驅動是路由器。
Binder 驅動
和路由器一樣,Binder驅動雖然默默無聞,卻是通信的核心。盡管名叫‘驅動’,實際上和硬件設備沒有任何關系,只是實現方式和設備驅動程序是一樣的:它工作于內核態,提供open(),mmap(),poll(),ioctl()等標準文件操作,以字符驅動設備中的misc設備注冊在設備目錄/dev下,用戶通過/dev/binder訪問該它。驅動負責進程之間Binder通信的建立,Binder在進程之間的傳遞,Binder引用計數管理,數據包在進程之間的傳遞和交互等一系列底層支持。驅動和應用程序之間定義了一套接口協議,主要功能由ioctl()接口實現,不提供read(),write()接口,因為ioctl()靈活方便,且能夠一次調用實現先寫后讀以滿足同步交互,而不必分別調用write()和read()。Binder驅動的代碼位于linux目錄的drivers/misc/binder.c中。
ServiceManager 與實名Binder
和DNS類似,SMgr的作用是將字符形式的Binder名字轉化成Client中對該Binder的引用,使得Client能夠通過Binder名字獲得對Server中Binder實體的引用。注冊了名字的Binder叫實名Binder,就象每個網站除了有IP地址外還有自己的網址。Server創建了Binder實體,為其取一個字符形式,可讀易記的名字,將這個Binder連同名字以數據包的形式通過Binder驅動發送給SMgr,通知SMgr注冊一個名叫張三的Binder,它位于某個Server中。驅動為這個穿過進程邊界的Binder創建位于內核中的實體節點以及SMgr對實體的引用,將名字及新建的引用打包傳遞給SMgr。SMgr收數據包后,從中取出名字和引用填入一張查找表中。
細心的讀者可能會發現其中的蹊蹺:SMgr是一個進程,Server是另一個進程,Server向SMgr注冊Binder必然會涉及進程間通信。當前實現的是進程間通信卻又要用到進程間通信,這就好象蛋可以孵出雞前提卻是要找只雞來孵蛋。Binder的實現比較巧妙:預先創造一只雞來孵蛋:SMgr和其它進程同樣采用Binder通信,SMgr是Server端,有自己的Binder對象(實體),其它進程都是Client,需要通過這個Binder的引用來實現Binder的注冊,查詢和獲取。SMgr提供的Binder比較特殊,它沒有名字也不需要注冊,當一個進程使用BINDER_SET_CONTEXT_MGR命令將自己注冊成SMgr時Binder驅動會自動為它創建Binder實體(這就是那只預先造好的雞)。其次這個Binder的引用在所有Client中都固定為0而無須通過其它手段獲得。也就是說,一個Server若要向SMgr注冊自己Binder就必需通過0這個引用號和SMgr的Binder通信。類比網絡通信,0號引用就好比域名服務器的地址,你必須預先手工或動態配置好。要注意這里說的Client是相對SMgr而言的,一個應用程序可能是個提供服務的Server,但對SMgr來說它仍然是個Client。
Client 獲得實名Binder的引用
Server向SMgr注冊了Binder實體及其名字后,Client就可以通過名字獲得該Binder的引用了。Client也利用保留的0號引用向SMgr請求訪問某個Binder:我申請獲得名字叫張三的Binder的引用。SMgr收到這個連接請求,從請求數據包里獲得Binder的名字,在查找表里找到該名字對應的條目,從條目中取出Binder的引用,將該引用作為回復發送給發起請求的Client。從面向對象的角度,這個Binder對象現在有了兩個引用:一個位于SMgr中,一個位于發起請求的Client中。如果接下來有更多的Client請求該Binder,系統中就會有更多的引用指向該Binder,就象java里一個對象存在多個引用一樣。而且類似的這些指向Binder的引用是強類型,從而確保只要有引用Binder實體就不會被釋放掉。通過以上過程可以看出,SMgr象個火車票代售點,收集了所有火車的車票,可以通過它購買到乘坐各趟火車的票-得到某個Binder的引用。
匿名 Binder
并不是所有Binder都需要注冊給SMgr廣而告之的。Server端可以通過已經建立的Binder連接將創建的Binder實體傳給Client,當然這條已經建立的Binder連接必須是通過實名Binder實現。由于這個Binder沒有向SMgr注冊名字,所以是個匿名Binder。Client將會收到這個匿名Binder的引用,通過這個引用向位于Server中的實體發送請求。匿名Binder為通信雙方建立一條私密通道,只要Server沒有把匿名Binder發給別的進程,別的進程就無法通過窮舉或猜測等任何方式獲得該Binder的引用,向該Binder發送請求。
參考鏈接:Android Bander設計與實現 - 設計篇 - universus的專欄 - 博客頻道 - CSDN.NET
總結
以上是生活随笔為你收集整理的Android之IPC机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是图像
- 下一篇: 【每日SQL打卡】