[筆記]Binder通信機(jī)制概述

IPC

我們知道,在操作系統(tǒng)中,進(jìn)程是最基本的單位,各自擁有獨(dú)立的內(nèi)存空間,所以進(jìn)程間無法直接訪問。
所以,各個(gè)操作系統(tǒng)都有跨進(jìn)程通信機(jī)制(pipe管道/signal消息/消息隊(duì)列/共享內(nèi)存/semaphore信號(hào)量/Socket套接字等),IPC就是所有跨進(jìn)程通信機(jī)制的統(tǒng)稱。
在Android中,也使用了一些傳統(tǒng)的IPC機(jī)制,例如Zygote進(jìn)程的IPC采用的是Socket套接字機(jī)制(AMS通過socket通知Zygote為應(yīng)用fork進(jìn)程),Android中的Kill Process采用的signal信號(hào)機(jī)制,但是,在system_server進(jìn)程和上層App層中,主要使用的是Binder通信機(jī)制。

Binder

Binder是Android中最常用的IPC之一,比如每個(gè)應(yīng)用的主線程ActivityThread和AMS系統(tǒng)進(jìn)程之間,就是使用binder通信(App進(jìn)程通過ActivityManagerNative引用的ActivityManagerProxy向system server發(fā)送bindler消息)
圖片來自startService源碼從AMS進(jìn)程到service的新進(jìn)程啟動(dòng)過程分析
  • 優(yōu)勢(shì)
    Android中使用Binder機(jī)制,一方面是為了提高效率(數(shù)據(jù)拷貝少+C和S相對(duì)獨(dú)立不干擾),一方面是為了加強(qiáng)安全性(通信雙方身份可以確定,Server可以通過UID/PID判斷是否滿足訪問權(quán)限)。
  • 原理

    Binder是一種C/S結(jié)構(gòu),Binder通信其實(shí)主要做兩件事:維護(hù)通信地址和建立信息通道,在Binder通信機(jī)制里,Service要注冊(cè)到系統(tǒng)內(nèi)核的Service Manager里(Service Manager也是一個(gè)系統(tǒng)進(jìn)程),Client從Service Manager里(實(shí)際上每個(gè)進(jìn)程都有Service Manager的代理對(duì)象)找到要通信的Service,相當(dāng)于找到通信地址;當(dāng)進(jìn)行通信時(shí),通過系統(tǒng)內(nèi)核提供的Binder驅(qū)動(dòng),在Client和Service直接建立連接。
    圖片來自官網(wǎng)
    圖片來自官網(wǎng)

    Client通過Server代理來訪問Server,Server代理會(huì)把Client傳遞的參數(shù)打包成Parcel對(duì)象,通過mmap映射,實(shí)現(xiàn)發(fā)送給內(nèi)核中的Binder驅(qū)動(dòng),Server端會(huì)自己去Binder驅(qū)動(dòng)力讀取數(shù)據(jù),從中找到發(fā)送給自己的Parcel數(shù)據(jù),解包處理。

    mmap映射過程其實(shí)是這樣的,跨進(jìn)程通信需要解決不同用戶之間不能共享內(nèi)存的問題,為此,mmap從接收方的用戶空間里映射一塊內(nèi)存出來,放到系統(tǒng)內(nèi)核,發(fā)送方的數(shù)據(jù)通過copy_from_user()復(fù)制到內(nèi)核空間時(shí),內(nèi)核會(huì)把它放到接收方映射出來的這塊兒內(nèi)存里,所以不需要用copy_to_user()再把數(shù)據(jù)從內(nèi)核向接收方復(fù)制一遍。


    Binder跨進(jìn)程

    如果要通過Binder傳遞的數(shù)據(jù)較大(1M),Binder就會(huì)借助匿名共享內(nèi)存Ashmem(Anonymous Shared Memroy)來傳遞,基本原理就是對(duì)同一塊兒物理內(nèi)存進(jìn)行映射,在各自進(jìn)程中映射為各自的虛擬內(nèi)存,這樣就可以在一個(gè)進(jìn)程中寫,在另一個(gè)進(jìn)程中讀,實(shí)現(xiàn)跨進(jìn)程通信的目的。
  • Bind Service
    Binder機(jī)制在Android中應(yīng)用非常廣泛,它在系統(tǒng)分層上位于Android Framwork層的下一層,所以跨進(jìn)程通信主要都是通過Binder實(shí)現(xiàn),比如Service和Activity的就可以通過Binder來建立通信,即使它們分處兩個(gè)進(jìn)程?;静襟E是這樣的:
    1.Service提供Binder對(duì)象
    在Service里自定義一個(gè)Binder類
public class YourBinder extends Binder {  
        YourService getService() { //通過Binder提供你的Service
            return YourService.this;  
        }  
    } 

然后在Service里的onBind函數(shù)返回IBinder對(duì)象(系統(tǒng)從Server Manager中檢索到IBinder對(duì)象)

public IBinder onBind(Intent intent) {
    return yourBinder;
}

2.Activity發(fā)起B(yǎng)indService
Context提供的函數(shù)bindService(intent,connection,flag),就是用來向系統(tǒng)申請(qǐng)建立Binder關(guān)系的,其中在intent里告訴系統(tǒng),由誰來提供目標(biāo)Binder(就是上一步中的Service,Service在onBind中告訴系統(tǒng)Binder)。
而參數(shù)connection如下:

private ServiceConnection mConnection = new ServiceConnection() {  
        public void onServiceConnected(ComponentName className, IBinder service) {
        ... 

在onServiceConnected回調(diào)函數(shù)中,IBinder service就是Service返回給系統(tǒng)的那個(gè)Binder對(duì)象。
Binder是一個(gè)C/S結(jié)構(gòu),這里的Activity就是C的角色,我們可以把多個(gè)
C給Bind到同一個(gè)S上,如果所有的C都和S解除了綁定(unBindService),并且Server本身不是startService起來的,系統(tǒng)會(huì)銷毀這個(gè)無人需要的Service。
3.雙向操作
IBinder service可以直接映射為Service中定義的Binder,然后獲取Service實(shí)例,這樣就能從Activity操作Service。

yourService = ((YourService.YourBinder) service).getService(); 

獲取到Service實(shí)例之后,想要從Service反向操作Activity,就可以自己想辦法實(shí)現(xiàn)了,比如通過接口實(shí)現(xiàn)回調(diào),這里就不詳述了。
整個(gè)過程大概是這樣的:


Bind Service

AIDL

開發(fā)者經(jīng)常遇到跨進(jìn)程通信的需求,如果自己實(shí)現(xiàn)Binder機(jī)制,工作量就比較大,系統(tǒng)提供了AIDL接口,可以讓我們更方便地實(shí)現(xiàn)Binder機(jī)制,用起來和在Activity里BindService很相似,主要過程如下:
1.新建aidl文件
new一個(gè)aidl文件,AS會(huì)在你的src/main/java目錄同級(jí)創(chuàng)建一個(gè)src/main/aidl目錄,然后在里面創(chuàng)建你的aidl文件及其所屬package目錄。
2.sync project
sync一下你的工程,AS會(huì)建立一個(gè)app/build/generated/source/aidl/目錄,里面自動(dòng)生成aidl文件對(duì)應(yīng)的java接口類,這個(gè)接口類與你的aidl文件同名。
3.生成Stub
系統(tǒng)生成的aidl接口中,最重要就是Stub抽象類,Stub抽象類擴(kuò)展android.os.Binder,同時(shí)實(shí)現(xiàn)你的aidl接口

public static abstract class Stub extends android.os.Binder implements IYourAidlInterface{
...

Stub里做了兩件重要的事:作為Binder接收和處理消息

public boolean onTransact(...){...}

以及向調(diào)用者提供Binder代理Proxy,以便讓對(duì)方給自己發(fā)消息

 private static class Proxy implements IYourAidlInterface {
        private android.os.IBinder mRemote;
        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }
       ...

其實(shí),Binder并不總是返回Proxy跨進(jìn)程代理,它會(huì)判斷你們是否在同一進(jìn)程內(nèi),如果在同一進(jìn)程內(nèi),Stub會(huì)直接給調(diào)用者一個(gè)接口對(duì)象(因?yàn)镾tub繼承了Binder,又實(shí)現(xiàn)了業(yè)務(wù)接口)。
4.做一個(gè)Service
在onBind里返回我們定義的aidlBindler接口實(shí)例,實(shí)際上是通過Service Manager在本地進(jìn)程中的代理,去Service Manager中尋找匹配的Service。
5.調(diào)用Binder
調(diào)用者需要自己實(shí)現(xiàn)一個(gè)ServiceConnection,通過context.bindService向系統(tǒng)要求綁定Binder,綁定成功后,就可以在ServiceConnection中通過回調(diào)函數(shù)獲取IBinder:

private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            yourInterface = IYourAidlInterface.Stub.asInterface(service);

這一步里,其實(shí)是Stub視雙方進(jìn)程情況,返回接口或Binder代理Proxy,Stub會(huì)通過這段代碼檢查進(jìn)程:

android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

如果是同一進(jìn)程內(nèi),不需要跨進(jìn)程,就直接提供一個(gè)nterface接口;如果是跨進(jìn)程,就提供一個(gè)Binder對(duì)象代理(IYourAidlInterface.Stub.Proxy)來實(shí)現(xiàn)跨進(jìn)程。
6.跨進(jìn)程傳輸
在跨進(jìn)程情況下,系統(tǒng)會(huì)使用Binder代理Proxy來傳輸數(shù)據(jù),Proxy里有一個(gè)IBinder接口對(duì)象。
Proxy傳遞數(shù)據(jù)其實(shí)就是調(diào)用IBinder對(duì)象,用IBinder對(duì)象的transact函數(shù)發(fā)出數(shù)據(jù),這就會(huì)觸發(fā)服務(wù)端的onTransact回調(diào)函數(shù)(在Stub里實(shí)現(xiàn)onTransact),服務(wù)端再把計(jì)算結(jié)果寫入_reply返回值,Proxy就能通過_reply拿到返回?cái)?shù)據(jù):

//向服務(wù)端發(fā)送數(shù)據(jù)
mRemote.transact(Stub.TRANSACTION_getInfor, _data, _reply, 0);
_reply.readException();
//接收服務(wù)端返回值
_result = _reply.readString();

所以,客戶端主要是通過Stub進(jìn)行通信,相關(guān)過程和關(guān)系大概是這樣的:


跨進(jìn)程時(shí)Stub的類和函數(shù)調(diào)用

與Intent的對(duì)比

Intent是另一種很常用的通信機(jī)制,一般會(huì)被打包為Parcel進(jìn)行傳輸,Intent與Binder相比,效率很低,因?yàn)樗前l(fā)送給系統(tǒng)進(jìn)程,系統(tǒng)對(duì)intent過濾后,找到目標(biāo)組件,再由系統(tǒng)把數(shù)據(jù)轉(zhuǎn)交給目標(biāo)組件,不像Bindler直接在C-S之間通信來得高效。
Intent有7個(gè)屬性:ComponentName、Action、Category、Data、Type、Extra以及Flag,這7種屬性可以分三類。
第一類:?jiǎn)?dòng)過濾,有ComponentName(顯式),Action(隱式),Category(隱式)。
第二類:?jiǎn)?dòng)模式,F(xiàn)lag。
第三類:數(shù)據(jù)傳值,有Data(隱式),Type(隱式),Extra(隱式、顯式)。
Intent分顯式和隱式兩種:

  • 顯式Intent就是用setClass或setComponent明確指定了啟動(dòng)哪個(gè)Activity或其他組件。所以顯式Intent效率高,但是耦合度高。
  • 隱式Intent就是不直接指定要啟動(dòng)的組件,而是通過設(shè)置Intent Filter過濾條件(Action+Category),由系統(tǒng)來篩選優(yōu)先級(jí)最高的那個(gè)Activity或其他組件,如果有多個(gè)最高優(yōu)先級(jí)的組件,會(huì)提示用戶手動(dòng)選擇。所以隱式Intent效率低,但是耦合度低。

隱式Intent必須有Action,并且在Category中至少有一個(gè)android.intent.category.DEFAULT,否則無法匹配過濾。
在數(shù)據(jù)傳值時(shí),Data其實(shí)是傳一個(gè)URI,如果想為這個(gè)URI定義一個(gè)TYPE,就需要把URI和TYPE一起傳給Intent,例如:intent.setDataAndType(Uri.parse(url), "audio/mp3");。
Intent還可以放Android系統(tǒng)剪切數(shù)據(jù),intent.setClipData(ClipData);

與ContentProvider對(duì)比

ContentProvider就更弱了,它只是作為數(shù)據(jù)接口,向其他進(jìn)程提供數(shù)據(jù),我們也可以通過android:multiprocess屬性,讓每個(gè)訪問進(jìn)程都自己創(chuàng)建一個(gè)ContentProvider實(shí)例,不用去跨內(nèi)存訪問,當(dāng)然,這樣會(huì)有內(nèi)存和數(shù)據(jù)同步問題,但是數(shù)據(jù)訪問效率高。
ContentProvider的基本原理是ASHMEM匿名共享內(nèi)存,ContentProvider保存的數(shù)據(jù)本來是不對(duì)其他進(jìn)程開放的,但是其他內(nèi)存可以創(chuàng)建一塊匿名共享內(nèi)存,然后用Binder將CursorWindow和共享內(nèi)存文件描述傳遞過來,讓ContentProvider也指向這塊兒共享內(nèi)存,從而實(shí)現(xiàn)跨進(jìn)程數(shù)據(jù)訪問。

引用

IPC、Binder、AIDL與Intent之間區(qū)別與聯(lián)系
Android Bander設(shè)計(jì)與實(shí)現(xiàn) - 設(shè)計(jì)篇
輕松理解 Android Binder,只需要讀這一篇
Android 中AIDL的使用與理解
一次搞定Process和Task
Android學(xué)習(xí)筆記——Activity的啟動(dòng)和創(chuàng)建

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容