IPC-程式間通訊

APLOMB發表於2018-11-29

開啟多程式

程式名以":"開頭的講程屬於當前應用的私有程式,其他應用的元件不可以和它跑在同一個程式中,而程式名不以開頭的程式屬於全域性程式, 其他應用通過ShareUID方式可以和它跑在同一個程式中。

我們知道Andrid系統會為每個應用分配一個唯一的UID,具有相同UID的應用才能共享資料。這裡要說明的是,兩個應用通過ShareUID跑在同一個程式中是有要求的,需要這兩個應用有相同的ShareUID並且簽名相同才可以。在這種情況下,它們可以互相訪問對方的私有資料,比如data目錄、元件資訊等,不管它們是否跑在同一個程式中。 當然如果它們跑在同一個程式中,那麼除了能共享data目錄、元件資訊,還可以共享記憶體資料,或者說它們看起來就像是一個應用 的兩個部分。

多程式造成問題

  • 靜態成員變數和單例物件完全失效
  • 執行緒同步失效
  • sp可靠性下降
  • Application多次建立

IPC方式

  • 使用Bundle
  • 共享檔案(sp不可靠,系統對sp的讀寫做了快取策略)
  • Binder
  • ContentProvider,天生支援跨程式
  • 網路通訊實現資料傳遞,socket
  • Messenger,底層由AIDL實現,一次處理一個請求(Handler)

Messenger

Messenger可以翻譯為信使,顧名思義,通過它可以在不同程式中傳遞Mesge物件, 在Message中放入我們需要傳遞的資料,就可以輕鬆地實現資料的程式間傳遞了

  • 服務端程式

    首先,我們需要在服務端建立一個Service來處理客戶端的連線請求,同時建立一個Handler並通過它來建立個Messenger物件,然後在Service的onBind中返回這個Messenger物件底層的Binder即可。

  • 客戶端程式

    客戶端程式中,首先要繫結服務端的Service, 繫結成功後用服務端返回的IBinder 對 象建立一個Messenger,通過這個Messenger 就可以向服務端傳送訊息了,發訊息型別為 Message 物件。如果需要服務端能夠回應客戶端,就和服務端一*樣,我們還需要建立 一個 Handler並建立一個新的Messenger, 並把這個Messenger物件通過Message的replyTo引數 傳遞給服務端,服務端通過這個replyTo引數就可以回應客戶端。

Binder

直觀來說,Binder 是Android中的一個類, 它實現了IBinder 介面。從IPC 角度來說, Binder是Android中的一種跨進 程通訊方式,Binder 還可以理解為一種虛擬的物理裝置,它的裝置驅動是/dev/binder,該通訊方式在Linux中沒有;從Android Framework角度來說Binder是ServiceManager連線各種Manager (ActivityManager、WindowManager,等等)相應ManagerService的橋樑;

從Android 應用層來說,Binder 是客戶端和服務端進行通化的媒介,當bindService 的時候,服務端會返回一一個包含了服務端業務呼叫的Binder物件通過這個Binder物件,客戶端就可以獲取服務端提供的服務或者資料,這裡的服務包括通服務和基於AIDL的服務。

Android開發中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Serv中的Binder不涉及程式間通訊,所以較為簡單,無法觸及Binder的核心,而Messenget底層其實是AIDL。

AIDL支援的資料型別

  • 基本資料型別
  • String和CharSequence
  • List,只支援ArrayList,裡面每個元素都必須能夠被AIDL支援
  • Map,只支援HashMap,裡面每個元素都必須能夠被AIDL支援
  • Parcelable,所有實現了Parcelable介面的物件
  • AIDL,所有的aidl介面本身也可以在adil檔案中使用
aidl注意
  • aidl檔案中使用了自定義的Parcelable物件和aidl物件必須顯式import進來,同一個包路徑下也要匯入

  • aidl檔案中使用了自定義的Parcelable物件,那必須建立一個和自定義的Parcelable物件同名的aidl檔案,並且在使用到這個物件的aidl檔案中聲名它為Parcelable型別

    parcelable User;
    複製程式碼
  • aidl檔案中每個實現Parcelable介面的類都需要聲名parcelable,之外除了基本資料型別,其他型別引數必須標上方向:in out inout

    • in:輸入型引數
    • out:輸出型引數
    • inout:輸入輸出引數
    interface IBookManager{
        void addBook(in Book book);
    }
    複製程式碼
  • aidl介面只支援方法,不支援宣告靜態常量

  • aidl的包結構在服務端和客戶端要保持一直.因為客戶端需要反序列化服務端中和aidl介面相關的所有類,類的完整路徑不一樣反序列化失敗

  • aidl方法是在服務端的Binder執行緒池中執行,CopyOnWriteArrayList自動執行緒同步,還有ConcurrentHashMap

  • RemoteCallbackList,自動執行緒同步,程式終止時自動移除註冊的listener;beginBroadcast()和finishBroadcast()必須配對使用

  • 服務端aidl介面方法全部執行在Binder執行緒池中,介面方法可以做耗時操作,如果呼叫介面的客戶端是在UI執行緒中,那麼不要再介面方法中做耗時操作否則就會導致ANR;如果明確知道必須做耗時操作,那麼在呼叫介面的客戶端中需要開啟執行緒來呼叫耗時的aidl介面方法

  • 客戶端中的aidl介面執行在客戶端的執行緒池中,不能做重新整理UI;

  • 服務端在UI執行緒中(比如在onBind方法中獲取客戶端的相關資訊)呼叫客戶端耗時的aidl介面,會導致服務端ANR

  • 客戶端的onServiceConnected()和onServiceDisconnected()執行在UI執行緒

Binder死亡、Binder連線斷裂
  • 兩種方法收到Binder死亡通知
    • 設定DeathRecipient監聽-->binderDied()
    • ServiceConnection-->onServiceDisconnected()方法
    • 二者區別
    • onServiceDisconnected()在客戶端的UI執行緒中被回撥,binderDied()在客戶端的Binder執行緒池中被回撥,binderDied()不能訪問UI
  • Binder提供兩個配對使用方法:linkToDeath()和unlinkToDeath(),通過linkToDeath方法設定一個死亡代理,Binder死亡時收到通知,此時可以重新發起請求重新連線
private IBinder.DeathRecipient recipient = new IBinder.DeathRecipient(){
    
    @override
    public void binderDied(){
        if(iBookManager == null){
            return;
        }
        iBookManager.asBinder().unlinkToDeath(recipient,0);
        iBookManager = null;
        //這裡重新繫結遠端Service
    }
};
複製程式碼

其次,在客戶端繫結遠端服務成功之後,給Binder設定死亡代理

 @Override
public void onServiceConnected(ComponentName name, IBinder binder) {
    iBookManager =  IBookManager.Stub.asInterface(binder);
    binder.linkToDeath(recipient,0);
}
複製程式碼
aidl許可權驗證
  • 在onBind()中通過自定義許可權驗證,沒有許可權直接返回null
  • 通過UID和PID驗證,使用getCallingUid()和getCallingPid()可以拿到客戶端所屬應用的UID和PID
  • 通過UID和PID這兩個引數拿到包名進行包名驗證
ContentProvider
//authority與表名關聯,根據傳入的URI取出來外界想要操作哪張表
//content://com.icbc.im.provider/user
//content://com.icbc.im.provider/book
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(authority,  path, code);
uriMatcher.addURI(authority,  path, code);
複製程式碼
  • onCreate()主執行緒
  • insert query update delete Binder執行緒池中
  • 資料發生改變通知外界
getContext().getContentResolver().notifyChange(uri, null);
複製程式碼

外界註冊資料改變監聽

getContext().getContentResolver().registerContentObserver(uri,true,new ContentObserver(null) {
            @Override
            public void onChange(boolean selfChange, Uri uri) {
                super.onChange(selfChange, uri);
            }
        });
//解除註冊
getContext().getContentResolver().unregisterContentObserver()複製程式碼


相關文章