Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發藝術-第二章 IPC 機制

Android開發藝術-第二章 IPC 機制

編輯:關於Android編程

2.1 Android IPC 簡介

IPC 意為進程間通信或者跨進程通信,線程是 CPU 調度的最小單元,是一種有限的系統資源。進程一般指一個執行單元。任何操作系統都需要相應的 IPC 機制。如 Windows 上可以通過剪切板 管道 和郵槽來進行;Linux 上可以通過命名管道 共享內容 信號量等來進行。在 Android 中最有特色的進程間通信方式就是 Binder 了,同時也支持 Socket 實現任意兩個終端之間的通信。

2.2 Android 中的多進程模式

(1) 通過給四大組件指定 android:process 屬性,可以開啟多線程模式,默認進程的進程名字是包名。

android:process=":sunquan"
android:process="cn.sunquan.com.xx"

“:”指當前的進程名前面附加上當前的包名,簡寫的方式。其次進程名以“:”開頭的進程屬於當前應用的私有進程,其他應用組件不可以跟它在同一個進程中,而進程名不以“:”開頭的進程屬於全局進程,其他的應用可以通過 ShareUID 方式和它跑到同一個進程中。
(2) 系統為每一個應用分配一個唯一的 UID,具備相同 UID 的應用才能共享數據。兩個應用通過 ShareUID 跑在同一個進程中需要有相同 ShareUID 的並且簽名相同。在這種情況下它們可以相互訪問對方的私有數據比如 data 目錄、 組件信息等,不管它們是否跑在同一個進程中。當然如果它們跑在同一個進程中,那麼除了能共享 data 目錄、組件信息,還可以共享內存數據,或者說它們看起來像一個應用的兩個部門。
(3) Android 為每一個應用分配了一個獨立的虛擬機,或者說為每一個進程都分配了一個獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這就導致在不同虛擬機上訪問同一個類的對象會產生多份副本。所以運行在不同進程中的四大組件,只要它們之間需要通過內存來共享數據,都會共享失敗,這就是多進程所帶來的主要影響。
(4) 多進程一般會造成如下幾方面的問題:
1. 靜態成員和單例模式完全失效:不在同一塊內存
2. 線程同步機制完全失效:不管鎖對象還是鎖全局類都無法保證線程同步,因為不同進程鎖的不是一個對象。
3. SharePreference 的可靠性下降:SharePreference 底層是通過讀/寫 XML 文件來實現,並發寫顯然可能出問題。SharePreference 不支持兩個進程同時執行寫操作,否則會有一定幾率的丟失。
4. Application 會多次被創建:當一個組件跑在一個新的進程中,系統會創建新的進程同時分配獨立的虛擬機,應用重啟一次,會創建新的 Application。運行在同一個進程中的組件屬於同一個虛擬機和同一個 Application。不同進程的組件的確會擁有獨立的虛擬機、Application 以及內存空間。同一應用的不同組件,運作在不同的進程中,那跟它們分別屬於兩個應用的部門沒有本質區別。
(5) 雖然不能直接共享內存但是通過跨進程通信還是可以實現數據交互。實現跨進程的方式:通過 Intent 來傳遞數據;共享文件;SharePreference;基於 Binder 的 Messager 和 AIDL 以及 socket。

2.3 IPC 基礎概念介紹

(1) Serializable 是 Java 所提供的一個序列化接口,為對象提供標准的序列化和反序列化操作。Parceable 接口是 Android 提供的序列化方式。
(2) 實現 Serializable 接口,並聲明一個 serialVersionUID 即可讓一個對象實現序列化。serialVersionUID 是一串long型的數字,用來輔助序列化和反序列化過程。原則上序列化後的數字中的 serialVersionUID 只有和當前累的 serialVersionUID 相同才能夠正常的被反序列化。serialVersionUID 的詳細工作機制:序列化得時候系統會把當前類的 serialVersionUID 寫入序列化得文件中(也可以是其他中介),當反序列化得時候系統會檢測文件中的 serialVersionUID,看它是否和當前類的 serialVersionUID 一致,如一致說明序列化的類的版本和當前類的版本相同,這個時候反序列化可以成功,否則說明當前類和序列化的類相比發生了某些變換,一般來說我們應該指定 serialVersionUID 的指。
注意:1.靜態成員變量屬於類不屬於對象,不參與序列化過程;2.用transient 關鍵字標記的成員變量不參與序列化過程。
(3) 實現 Parceable 接口,一個類的對象可以通過實現序列化並可以通過 Intent 和 Binder 傳遞。Pacel 內部包裝了可序列化的數據,可以在 Binder 中自由傳輸,可以直接序列化得有 Intent、Bundle、Bitmap、List、Map等,前提是它們裡面的每一個元素都是可序列化的。
(4) Serializable 和 Parceable 的區別:Serializable 是 Java 中的序列化接口,其使用起來簡單但是因為大量 I/O 操作而開銷大。Parceable 是 Android 中的序列化方式,更適用在 Android 平台上,缺點是使用起來麻煩,但是效率高。Parceable 主要用在內存序列上,而序列化到存儲設備上或者序列化後通過網絡傳輸則建立適用 Serializable。
(5) Binder 是 Android 中的一個類,它實現了 Binder 接口。從 IPC 角度來說,Binder 是 Android 中一種跨進程通信方式,Binder 還可以理解為一種虛擬的物理設備,設備驅動是 /dev/binder,該通信方式在 Linux 中沒有;從 Android Framework 角度來說,Binder 是ServiceManager 連接各種 Manager 和相應 ManagerService 的橋梁;從 Android 應用層來說,Binder 是客戶端和服務端進行通信的媒介,當 bindService 的時候,服務端會返回一個包含了服務端業務調用的 Binder 對象,通過這個 Binder 對象,客戶端就可以獲取服務端提供的服務或數據,這裡的服務包括普通服務和基於 AIDL 的服務。
Android 開發中,Binder 主要用在 Service 中,包括 AIDL 和 Messager,其中普通 Service 中的 Binder 不涉及進程間的通信,較為簡單。而 Messager 的底層其實也是 AIDL。
(6) aidl工具根據 aide文件自動生成 Java 接口的解析:聲明了幾個接口的方法,同時聲明幾個整型 id 來標識這幾個接口,id 用來標識在 transact 過程中客戶端請求的是哪個方法。接著會聲明一個 內部類 Stub ,這個 Stub 就是一個 Binder 類, 當客戶端和服務器位於同一個進程中,則不會走 跨進程的 transact 過程,如果不在同一個進程,方法調用需要走 transact 過程,這個邏輯有 Stub 內部代理 Proxy 來完成。
其核心實現就是它的內部類 Stub 和 Stub 內部代理 Proxy。其幾個方法分析如下:
1. asInterface (android.os.Binder obj):用於將服務端的 Binder 對象轉換成客戶端所需的 AIDL 接口類型的對象。如果客戶端和服務端位於同一進程,那麼此方法返回服務端的 Stub 對象本身,否則返回系統封裝後的 Stub.proxy 對象。
2. asBinder:用於放回當前 Binder 對象
3. onTransact :運行在服務端中的 Binder 線程中,當客戶端發起跨進程請求中,遠程請求會通過系統底層封裝後由此方法來處理。該方法的原型為 public Boolean onTranact (int code, android.os.Parcel data, android.os.Parcel reply, int flags)。服務端通過 code 可以確定客戶端所請求的目標方法是什麼,接著從 data 中取出目標方法所需的參數(如果目標方法有參數的話),然後執行目標方法。當目標方法執行完畢後,就向 reply 中寫入返回值(如果目標方法有返回值的話)。如果此方法返回 false,那麼客戶端的請求會失敗,可以通過這個特性做權限驗證。
4. Proxy#[method] :運行在客戶端,當客戶端調用此方法時,首先創建該方法所需的輸入型 Parcel 對象 _data、輸出型 Parcel 對象 _repley 和返回值對象,把該方法的參數信息寫入 _data 中(如果有參數),然後調用 transact 方法發起 RPC(遠程過程調用)請求,同時當前線程掛起,然後服務端的 onTransact 方法會被調用,直到 RPC 過程返回後,當前線程繼續執行,並從 _reply 中取出 RPC 過程的返回結果。最後返回 _reply 中的數據。
注意:1.當客戶端發起請求時,由於當前線程會被掛機直到服務端進程返回數據,如果遠程方法耗時,那麼不能在 UI 線程中發起此遠程請求。2.服務端的 Binder 方法運行在 Binder 的線程池中,不管 Binder 方法是否耗時都應采用同步的方式去實現,因為運行在一個線程中。
5. AIDL 文件的本質就是系統提供一種快速實現 Binder 的工具,我們可以自己手動寫,也可以通過 AIDL 文件讓系統自動生成。
6. Binder 有兩個很重要的方法:linkToDeath 和 unlinkToDeath,Binder 運行在服務端,服務端進程由於某些原因異常終止了,服務端的 Binder 連接斷裂,導致客戶端遠程調用失敗。通過 linkToDeath 可以給 Binder 設置一個死亡代理,當 Binder 死亡時,會收到通知,這時可以重新發起連接請求從而恢復連接。
設置 Binder 死亡代理如下:
首先聲明一個 DeathRecipient 對象,DeathRecipient 是一個接口,內部只有一個方法 binderDied,當 Binder 死亡時候,系統回調此方法,我們可以在移除之前綁定的 binder 代理並重新綁定遠程服務。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (mRemoteBookManager == null) return;        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mRemoteBookManager = null;
        // TODO:這裡重新綁定遠程Service
    }
};

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

mservice= IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0)

其中 linkToDeath 的第二個參數是個標記位,直接設置0即可。此外通過 Binder 的方法 isBinderAlive 也可以判斷 Binder 是否死亡。

2.4 Android 中的 IPC 方式

(1) 使用 Bunble:Bunble 實現了 Parcelable 接口,可以在不同的進程間傳輸。Bunble 不支持的類型無法通過它在進程間傳輸。
(2) 使用文件共享:由於 Android 系統基於 Linux,並發讀/寫文件可以沒有限制的進行,兩個線程對同一個文件進行寫操作都是運行,但可能出問題。文件共享方式適合對數據同步要求不高的進程間進行通信,並要妥善處理並發讀/寫的問題。SharedPreference 是一個特例,屬於文件的一種,但系統對其的讀/寫有一定的緩存策略,即在內存中會有一份 SharedPreference 文件的緩存,因此在多進程下,系統對它的讀/寫變得不可靠,面對高並發的讀/寫訪問,SharedPreference 有很大幾率丟失數據,所以不建議在進程間通信中使用 SharedPreference。
(3) 使用 Messenger:Messenger 是一種輕量級的 IPC 方案,其底層實現是 AIDL,以串行方式處理客戶端發來的消息,其服務端一次處理一個請求,不存在並發執行的情形(對於有大量並發請求,Messenger 就不合適適用)。
(4) 使用 AIDL :首先服務端創建一個 Service 用來監聽客戶端的連接請求,創建一個 AIDL 文件,將暴露給客戶端的接口在這個 AIDL 文件中聲明,最後 Service 中實現這個 AIDL接口。客戶端綁定服務端 Service,建立連接就可訪問遠程服務端的方法。
1. AIDL 支持的數據類型:基本數據類型(int、long、chat、boolean、double 等);String 和 CharSequence;List(只支持 ArrayList,裡面的子元素都必須能夠被 AIDL 支持);Map(只支持 HashMap,裡面每個元素都必須被 AIDL 支持,包括 key 和 value);Parcelable(所以實現了 Parcelable 接口的對象);AIDL(所有的 AIDL 接口也可以在 AIDL 文件中使用)。
2. 自定義的 Parcelable 對象和 AIDL 文件就算和當前 AIDL 文件位於同一包內也要顯式 import。
3. 如果 AIDL 文件中用到自定義 Parcelable 對象,必須新建一個和它同名的 AIDL 文件,並在其中聲明它為 Parcelable 類型。
4. AIDL 中除了基本數據類型,其他類型的參數必須標上方向:in、out 或者 inout,in 表示輸入類型參數,out 表示輸出型參數。
5. AIDL 接口中只支持方法,不支持聲明靜態常量。區別於傳統的接口。
6. 為方便 AIDL 的開發,建議把所有和 AIDL 相關的類和文件全部放入同一個包中,當客戶端是另一個應用時,可以直接把整個包復制到客戶端工程中。
7. RemoteCallbackList 是系統專門提供用於刪除進程 listener 的接口,RemoteCallbackList 是一個泛型,支持管理任意的 AIDL 接口,所有的 AIDL 接口都繼承自 Interface 接口,在它的內部有一個 Map 結構專門用來保存所有的 AIDL 回調,其 Map 的 key 是 Binder 類型,value 是 Callback 類型。當客戶端進程終止後,它能夠自動移除客戶端所注冊的 listener。另外 RemoteCallbackList 內部自動實現了線程同步的功能,所以使用它進行注冊和解注冊時,不需要額外的線程同步工作。使用 RemoteCallbackList 需要注意是:它不是一個 List。遍歷 RemoteCallbackList 需要按照以下方式進行,其中 beginBroadcast 和 finishBroadcast 必須配對使用,哪怕僅僅是獲取 RemoteCallbackList 中的元素個數:

int N =mListenerList.beginBroadcast();
   for(int i = 0; i < N; i++){
            //         
     }
    mListenerList.finishBroadcast();
客戶端的 onServiceConnected 和 onServiceDisconnected 方法都運行在 UI 線程中,所以不可以在它們裡面直接調用服務端的耗時方法。服務端的方法本身就運行在服務端的 Binder 線程池中,所以服務端方法本身就可以執行大量耗時操作,這個時候切記不要在服務端方法中開線程去進行異步任務,除非明確干什麼。 Binder 可能意外死亡,需要重新連接服務,有兩種方法:給 Binder 設置 DeathRecipient 監聽,死亡時會收到 binderDied 方法回調,然後可以重連;一種是在 onServiceDisconnected 中重連。其二者區別:onServiceDisconnected 在客戶端的 UI 線程中被回調,binderDied 在客戶端的 Binder 線程池中被回調,不能訪問 UI。 AIDL 使用常用的權限驗證方法: 一是在 onBind 中進行驗證,驗證不通過返回 null,客戶端直接無法綁定服務。驗證方式如 使用 permission 驗證,在 AndroidMenifest 中聲明所需權限(自定義 permission),此方式同樣適用於 Messenger 中。二是在服務端 onTransact 方法中進行權限驗證,如果驗證失敗返回 false,這樣服務端就不會終止執行 AIDL 中的方法從而達到保護服務端的效果,驗證方式也可以采用permission,還可以采用 Uid 和 Pid 來做驗證,通過 getCallingUid 和 getCallingPid 拿到客戶端所屬應用的 Uid 和 Pid。這個兩個參數可以用來做一些驗證工作,比如驗證包名。
(5) 使用 ContentProvider:ContentProvider 是 Android 中提供的專門用於不同應用間進行數據共享方式,和 Messenger 一樣,其底層實現是 Binder,當其實現過程比 AIDL 簡單許多。主要以表格的形式來組織數據,可以包含多個表。還支持文件數據,比如圖片和視頻等。文件數據和表格數據的結構不同,因此處理此類數據可以在 ContentProvider 中返回文件的句柄給外界從而讓文件來訪問 ContentProvider,Android 系統提供的 MediaStore 功能就是文件類型的 ContentProvider。ContentProvider 的底層數據看起來像一個 SQLite 數據庫,但是 ContentProvider 對底層的數據存儲方式沒有任何要求,可以使用 SQLite 數據庫,也可以使用普通文件,甚至可以采用內存中的一個對象進行數據的存儲。要觀察一個 ContentProvider 中的數據改變情況,可以通過 ContentResolver 的 registerContentObserver 方法來注冊觀察者,可以通過 unregisterContentObserver 方法來解除觀察者。
(6) 使用 Socket : Socket 被稱為套接字,分為流式套接字和用戶數據報套接字兩種。分別對應於網絡的傳輸控制層中的 TCP 和 UDP 協議。

2.5 Binder 連接池

當項目到一定程度,無限制的增加 Service 是不對的,Service 是四大組件之一,也是一種系統資源。我們需要將所有的 AIDL 放在同一個 Service 中去管理。工作機制:每個業務模塊創建自己的 AIDL 接口並實現此接口,不同的業務模塊之間是不能有耦合的,所有實現細節都單獨開來,然後向服務端提供自己的唯一標識和其相對應的 Binder 對象。對於服務端來說只需要一個 Service 就可以了,服務端提供一個 queryBinder 接口,這個接口能夠根據業務模塊的特征來返回相應的 Binder 對象給它們。不同的業務模塊拿到所需的 Binder 對象後就可以進行遠程方法調用,由此可見,Binder 連接池的主要作用就是將每個業務模塊的 Binder 請求同一轉發到遠程 Service 中去執行,從而避免重復創建 Service。建議在開發中使用 BinderPool。

2.6 選用合適的 IPC 方式

這裡寫圖片描述

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved