Android開發藝術探索 第2章 IPC機制 讀書筆記


一個讀書的小Tips:如果對原書內容不是很了解的小伙伴,可以先看讀書筆記,心中有個概要,然后再細讀原書;


學習內容:開篇介紹了Android中的多進程概念已經多進程開發中常見的注意事項,接著介紹了Android序列化和Binder,以及詳細介紹了Bundle、文件共享、AIDL、Messenger、ContentProvider和Socket這幾種進程間通訊;還講解了如何使用Binder連接池來達到多個AIDL共用一個Service。最后講解了各種進程間通訊的優缺點和適用場景。接下來 咱們開始吧


2.1 Android IPC簡介

  • IPC為進程間通訊,兩個進程之間進行數據交換的過程。
  • IPC不是Android所獨有的,任何一個操作系統都有對應的IPC機制。Windows上通過剪切板、管道、油槽等進行進程間通訊。Linux上通過命名空間、共享內容、信號量等進行進程間通訊。Android中沒有完全繼承于Linux,有特色的進程間通訊方式是Binder,還支持Socket。
  • 使用場景:由于某些原因應用自身需要采用多進程模式來實現,或者為了加大一個應用可使用的內存,因為Android對當個應用可使用的最大內存做了限制。

2.2 Android中的多進程模式

  • 同一個應用,通過給四大組件指定android:process屬性,就可以開啟多進程模式。
  • 進程名以":"開頭的屬于當前應用的私有進程,其他應用的組件不可以和他跑在同一個進程里面。而進程名不以":"開頭的進程屬于全局進程,其他應用通過ShareUID方式可以和它跑在同一個進程中。兩個應用可以通過ShareUID跑在同一個進程并且簽名相同,他們可以共享data目錄、組件信息、共享內存數據。
  • 多進程通訊的問題
  1. 靜態成員和單例模式完全失效。
  2. 線程同步機制完全失效。
  3. SharedPreferences的可靠性下降
  4. Application會多次創建
    問題1、2原因是因為進程不同,已經不是同一塊內存了;
    問題3是因為SharedPreferences不支持兩個進程同事進行讀寫操作,有一定幾率導致數據丟失;
    問題4是當一個組件跑在一個新的進程中,系統會為他創建新的進程同時分配獨立的虛擬機,所有這個過程其實就是啟動一個應用的過程,,因此相當于系統又把這個應用重新啟動了一遍,Application也是新建了。
    實現跨進程通訊有很多方式共享文件、SharedPreferences、基于Binder的Messenger和AIDL、Socket等。

2.3 IPC基礎概念介紹

2.3.1 Serializable接口
  1. Serializable是Java所提供的一個序列化接口
  2. serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化后的數據中的serialVersionUID要和當前類的serialVersionUID相同才能正常的序列化。
  3. 靜態成員變量屬于類不屬于對象,所以不會參加序列化過程;其次用transient關鍵字標明的成員變量也不參加序列化過程。
  4. 重寫如下兩個方法可以重寫系統默認的序列化和反序列化過程
private void writeObject(java.io.ObjectOutputStream out)throws IOException{
}
private void readObject(java.io.ObjectInputStream out)throws IOException,ClassNotFoundException{
}
2.3.2 Parcelable接口
  1. Android中特有的序列化方式,效率相對Serializable更高,占用內存相對也更少,但使用起來稍微麻煩點。
public class Book implements Parcelable {
    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
    public int code;
    public String name;
    public Book(int code, String name) {
        this.code = code;
        this.name = name;
    }
    protected Book(Parcel in) {
        code = in.readInt();
        name = in.readString();
    }
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(code);
        dest.writeString(name);
    }
}
  1. 序列化功能由writeToParcel方法來完成,最終通過Parcel中的一系列write方法完成的。反序列化功能由CREATEOR來完成,其內部標明了如何創建序列號對象和訴諸,并通過Parcel的一系列read方法來完成反序列化過程。內容描述功能由describeContents方法來完成,幾乎所有情況都返回0,只有當前對象存在文件描述符時,才返回1。
2.3.3 Binder
  1. 從IPC的角度來說,Binder是Android的一種跨進程的通訊方式;Binder也可以理解為是一種虛擬額物理設備,他的設備驅動是/dev/binder;從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager、等等)和ManagerService的橋梁;從Android應用層來說,Binder是客戶端與服務端通訊的媒介。在Android開發中,Binder主要用于Service中,包括AIDL和Messenger,其中普通的Service的Binder不涉及進程間通訊;而Messenger的底層其實就是AIDL。
  2. 系統會根據AIDL文件生成同名的.java類;首先聲明與AIDL文件相對應的幾個接口方法,還申明每個接口方法相對應的整形id來做為標識,這幾個id在transact過程中標識客戶端請求的到底是什么方法。接著會聲明一個內部類Stub,這個Stub就是一個Binder類,當客戶端和服務端處于同一個進程的時候,方法調用不會走transact過程,處于不同進程時,方法調用會走transact過程,這個邏輯由Stub的內部代理類Proxy來完成。所以核心實現在于它的內部類Stub和Stub的內部代理類Proxy,下面分析其中的方法:
  3. DESCRIPTOR Binder的唯一標識,一般用當前Binder的類名表示。
  4. asInterface(android.os.IBinder obj) 將服務端的Binder對象轉換成客戶端所需要的AIDL接口類型的對象;如果客戶端和服務端位于相同進程,那么此方法返回的就是服務端Stub對象本身,否則返回系統封裝后的Stub.proxy對象。
  5. asBinder 用于返回當前的Binder對象
  6. onTransact 運行在服務端的Binder線程池中,當客戶端發起跨進程通訊時,遠程請求會通過系統底層封裝交由此方法處理。
public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)

服務端通過code確認客戶端請求的目標方法是什么,接著從data中取出目標方法所需的參數(如果有),然后執行目標方法。當目標方法執行完后,向reply中寫入返回值(如果有)。如果方法返回值為false,那么服務端的請求會失敗,利用這個特性我們可以來做權限驗證
5.Proxy#[Method] 代理類中的接口方法。首先創建該方法所需要的輸入型參數Parcel對象_data和輸出型參數Parcel對象_reply,然后把參數寫入_data中,接著調用transact方法來發起RPC(遠程過程調用)請求,同時當前線程掛起;然后服務端的onTransace方法會被調用直到RPC過程返回后,當前線程繼續執行,并從_reply中去除RPC過程的返回結果。

  1. Binder的兩個重要方法linkToDeathunlinkToDeath。通過linkToDeath可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知,然后就可以重新發起連接請求。聲明一個DeathRecipient對象,DeathRecipient是一個接口,其內部只有一個方法binderDied,實現這個方法后就可以在Binder死亡的時候收到通知了。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
    @Override
    public void binderDied(){
        if(mBookManager == null){
            return;
        }
        mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
        mBookManager = null;
        // TODO:接下來重新綁定遠程Service
    }
}

在客戶端綁定遠程服務成功后,給Binder設置死亡代理

mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
  1. 當清楚AIDL接口文件的結構和作用后,是可以不通過AIDL而直接實現Binder來進行跨進程通訊的。細節請參考原書以及隨書代碼

2.4 Android的IPC方式

  1. 使用Bundle:由于Binder實現了Parcelable接口,所以可以方便的在不同進程中傳輸;Activity、Service和Receiver都支持在Intent中傳遞Bundle數據。
  2. 使用文件共享:兩個進程通過讀/寫一個文件來交換數據;適合對數據同步要求性不高的場景;并要避免并發寫這種場景或者處理好線程同步問題。SharedPreferences是個特例,雖然也是文件的一種,但系統在內存中有一份SharedPreferences文件的緩存,因此在多線程模式下,系統對他的讀/寫就變得不可靠,高并發讀寫SharedPreferences有一定幾率會丟失數據,因此不建議在多進程通訊時采用SharedPreferences。
  3. 使用Messenger:Messenger是輕量級的IPC方案,底層實現是AIDL,他對AIDL進行了封裝,Messenger 服務端是以串行的方式來處理客戶端的請求的,不存在并發執行的情形。
  4. 使用AIDL服務端首先創建一個Service用來監聽客戶端的連接請求,然后創建一個AIDL文件,將暴露給客戶端的接口在AIDL文件中聲明,最后在Service中實現這個AIDL接口即可。客戶端首先綁定服務端的Service,綁定成功后,將服務端返回的Binder對象轉化成AIDL接口所屬的類型,調用相對應的AIDL中的方法。
  5. AIDL支持的數據類型:
    基本數據類型;
    String、CharSequence;
    List 只支持ArrayList,里面的元素必須都能被AIDL所支持;
    Map 只支持HashMap,里面的元素(key和value)必須都能被AIDL所支持;Parcelable 所有實現了Parcelable接口的對象;
    AIDL 所有AIDL接口本身也可以在AIDL文件中使用。
  6. 自定義的Parcelable對象和AIDL對象必須顯示的import進來(即使在同一個包)。
  7. 除了基本數據類型,需要用inout表示輸入輸出型參數。
  8. 為了方便AIDL開發,建議把所有和AIDL相關的類和文件都放在同一個包中,好處在于,當客戶端是另一個應用的時候,我們可以直接把整個包復制到客戶端工程中去。
  9. RemoteCallbackList是系統專門提供用于刪除跨進程listener的接口,RemoteCallbackList是泛型,支持管理任意的AIDL接口,因為所有AIDL接口都繼承自android.os.IInterface接口。
  10. 需注意AIDL客戶端發起RPC過程的時候,客戶端的線程會掛起,如果是UI線程發起的RPC過程,如果服務端處理事件過長,就會導致ANR。
  • 使用ContentProvider
    ContentProvider是Android專門用于不同應用之間進行數據共享的方式,天生適合跨進程通訊,底層同樣采用Binder實現。
    ContentProvider主要以表格的形式來組織數據,可以包含多個表;ContentProvider支持普通文件,甚至可以采用內存中得一個對象來進行數據存儲。
    通過ContentProvider的notifyChange方法來通知外界當前ContentProvider中的數據已經發生改變。
  • 使用Socket
    Socket也被稱為“套接字”,是網絡通訊中得概念,分為流式套接字和用戶數據報套接字兩種,分別對應網絡的傳輸控制層中得TCP和UDP協議。

以上內容詳細的代碼示例請參照原書和隨書源碼喲。


2.5 Binder連接池

  1. 當項目越來越龐大后,需要使用到的AIDL接口文件也越來越多,但我們不能有多少個AIDL就添加多少個Service,Service是四大組件之一,是一種系統資源,太多的Service會讓我們的App看起來很重量級;我們應該把所有AIDL放在一個Service中去管理。 這時候不同業務模塊之間是不能有耦合的,所有實現細節需要單獨來開,然后向服務端提供一個queryBInder接口,這個接口根據業務模塊的特征來返回Binder對象給它們,不同的業務模塊拿到所需的Binder對象給它們,不同的業務模塊拿到所需的Binder對象后就可以進行遠程方法調用了;由此可見Binder連接池的主要作用就是將每個業務模塊的Binder請求統一轉發到遠程Service中去執行。
  2. 當新業務模塊加入新的AIDL,那么在它實現自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,給自己添加一個新的binderCode并返回相對應的Binder對象即可,不需要添加新的Service。作者建議在AIDL開發過程中引入BinderPool機制。 詳細源碼請參考原書和隨書源碼

2.6 選用合適的IPC方式

IPC方式的優缺點和適用場景.PNG
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容