Android Binder機(jī)制掃盲

由于Android系統(tǒng)保護(hù)機(jī)制(沙箱機(jī)制)雄驹,兩個(gè)進(jìn)程是各自運(yùn)行在自己的進(jìn)程空間之中的,相互之間進(jìn)行隔離并不能夠直接進(jìn)行通訊(確保一個(gè)進(jìn)程掛掉了职抡,不會影響另外一個(gè)進(jìn)程的運(yùn)行)鹃锈。

一、為什么使用Binder機(jī)制

Android系統(tǒng)是基于Linux系統(tǒng)的宪萄,我們知道Linux 系統(tǒng)之中常見的進(jìn)程之間的通訊就有共享內(nèi)存艺谆、消息隊(duì)列、管道拜英、Socket等静汤。那為什么Android 系統(tǒng)不直接采用Linux 的進(jìn)程之間的通訊方式,而是采用Binder機(jī)制,C/S模式來實(shí)現(xiàn)進(jìn)程之間的通訊呢虫给?

我想主要有以下三個(gè)方面的原因藤抡。

  • C/S架構(gòu)模式,Binder能夠很好的支持Client-Server模式抹估,相比只有Socket能夠提供C/S模式缠黍,但是Socket主要是用于網(wǎng)絡(luò)之間的通訊以及本地進(jìn)程之間的低速通訊,效率太低药蜻。
  • 安全考慮瓷式, Android 系統(tǒng)為每一個(gè)應(yīng)用程序都分配了唯一的一個(gè)UID,且Android系統(tǒng)安全權(quán)限管理很嚴(yán)格语泽,Server端會根據(jù)權(quán)限控制策略贸典,判斷UID/PID是否滿足訪問權(quán)限。
  • 性能考慮踱卵, 傳統(tǒng)的消息隊(duì)列廊驼、管道、Socke都需要通過2次拷貝操作惋砂,才能實(shí)現(xiàn)從用戶態(tài)空間到內(nèi)核空間的數(shù)據(jù)拷貝妒挎。而共享內(nèi)存實(shí)現(xiàn)方式復(fù)雜,沒有客戶與服務(wù)端之別西饵, 需要充分考慮到訪問臨界資源的并發(fā)同步問題饥漫,否則可能會出現(xiàn)死鎖等問題。而Binder只需要一次拷貝操作就可以(從發(fā)送方的緩存區(qū)拷貝到內(nèi)核的緩存區(qū)罗标,而接收方的緩存區(qū)與內(nèi)核的緩存區(qū)是映射到同一塊物理地址的,因此只需要1次拷貝即可)积蜻。
二闯割、幾個(gè)重要的概念
Binder中各角色之間關(guān)系
  • Binder實(shí)體,其實(shí)就是Server在內(nèi)核中的binder_node結(jié)構(gòu)體的對象竿拆,保存著Server對象在用戶空間的地址信息宙拉。通過Binder實(shí)體可以找到用戶空間的Server的對象。
  • Binder引用丙笋,其實(shí)就是是內(nèi)核中binder_ref結(jié)構(gòu)體的對象谢澈,它的作用是在表示"Binder實(shí)體"的引用。簡單說是每一個(gè)Binder引用都是某一個(gè)Binder實(shí)體的引用御板,通過Binder引用可以在內(nèi)核中找到它對應(yīng)的Binder實(shí)體锥忿。
  • 遠(yuǎn)程服務(wù),Server都是以服務(wù)的形式注冊到ServiceManager中進(jìn)行管理的怠肋,可以理解就是Server提供的服務(wù)敬鬓。
  • ServiceManager,是用戶空間的守護(hù)進(jìn)程,由init進(jìn)程負(fù)責(zé)啟動(dòng)钉答,之后會打開/dev/binder設(shè)備础芍,建立128K虛擬內(nèi)存映射Binder 驅(qū)動(dòng),下發(fā)BINDER_SET_CONTEXT_MGR的command数尿,聲明自己成為上下文管理者仑性,進(jìn)入binder消息輪詢,等待client消息到來右蹦。
  • Server注冊服務(wù)诊杆,Server首先會向Binder驅(qū)動(dòng)發(fā)起注冊服務(wù)請求,Binder驅(qū)動(dòng)會新建與該Server對應(yīng)的Binder實(shí)體嫩实,在ServiceManager的保存Binder引用的紅黑樹中查找Server的Binder引用如果不存在則會新建一個(gè)與該Server對應(yīng)的Binder引用刽辙。并將其添加到ServiceManager的保存Binder引用的紅黑樹之中。
  • Client獲取遠(yuǎn)程服務(wù)甲献,Client攜帶Server的服務(wù)名向Binder驅(qū)動(dòng)獲取遠(yuǎn)程服務(wù)宰缤,Binder驅(qū)動(dòng)會轉(zhuǎn)發(fā)會給ServiceManager進(jìn)程,且有ServiceManager返回Server對應(yīng)的Binder實(shí)體的Binder引用信息晃洒,Client根據(jù)這個(gè)信息創(chuàng)建Server的遠(yuǎn)程服務(wù)慨灭,該遠(yuǎn)程服務(wù)會對應(yīng)的通過Binder驅(qū)動(dòng)和真正的Server進(jìn)行交互,從而執(zhí)行相應(yīng)的動(dòng)作球及。
三氧骤、AIDL 示例

在Android開發(fā)過程中,我們一般是采用編寫AIDL文件的形式來描述服務(wù)器提供哪些接口吃引。
IDataManager.aidl 文件

// 無論應(yīng)用的類是否和aidl文件在同一包下筹陵,都需要顯示import
import org.github.lion.aidl_demo.Data;
interface IDataManager {
    /** AIDL 支持的數(shù)據(jù)類型劃分為四類
     * 第一類是 Java 編程語言中的基本類型
     * 第二類包括 String、List镊尺、Map 和 CharSequence
     * 第三類是其他 AIDL 生成的 interface
     * 第四類是實(shí)現(xiàn)了 Parcelable protocol 的自定義類
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean
                    , float aFloat, double aDouble, String aString);
    int getDataTypeCount();
    List<Data> getData();
    String getUrlContent(String url);
}

自定義實(shí)現(xiàn)Parcelable接口類型

/** 必須實(shí)現(xiàn)Parcelable接口
 *  1. 定義CREATOR對象
 */
public class Data implements Parcelable {

    ...

    protected Data(Parcel in) {
        ...
    }

    public static final Creator<Data> CREATOR = new Creator<Data>() {
        @Override
        public Data createFromParcel(Parcel in) {
            return new Data(in);
        }

        @Override
        public Data[] newArray(int size) {
            return new Data[size];
        }
    };
}

服務(wù)端的實(shí)現(xiàn)

private static final IDataManager.Stub mBinder = new IDataManager.Stub() {

    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean
                           , float aFloat, double aDouble, String aString) 
                           throws RemoteException {
    }

    @Override
    public int getDataTypeCount() throws RemoteException {
        // todo return some data
        return 0;
    }

    @Override
    public List<Data> getData() throws RemoteException {
        // todo return some data
        return null;
    }

    @Override
    public String getUrlContent(String url) throws RemoteException {
        // todo return some data
        return null;
    }
};

@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

客戶端實(shí)現(xiàn)

private IDataManager dataManagerService = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // 綁定服務(wù)
    bindService(new Intent(this, DataManagerService.class), dataServiceConnection,
            Context.BIND_AUTO_CREATE);
}

private ServiceConnection dataServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {       
        // 返回IBinder 對象朦佩,封裝為代理類
        dataManagerService = IDataManager.Stub.asInterface(service);
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        dataManagerService = null;
    }
};

接下來我們看看Android Studio幫我們自動(dòng)生成的Java文件里面的內(nèi)容。

// IDataManager2 直接繼承了IInterface
public interface IDataManager2 extends IInterface {
    // 返回值為基本數(shù)據(jù)類型庐氮,定義接口時(shí)不需要做特殊處理
    int getDataCount() throws RemoteException;
    // 自定義的返回?cái)?shù)據(jù)類型需要實(shí)現(xiàn)Parcelable接口语稠,進(jìn)程間通信不能直接共享內(nèi)存,需要將對象持久化弄砍。
    // 所以自定義的類需要實(shí)現(xiàn)Parcelable接口
    List<Data2> getData() throws RemoteException;
}

//內(nèi)部抽象類集成Binder實(shí)現(xiàn)了IDataManager2(Stub)接口
public abstract class DataManagerNative extends Binder implements IDataManager2 {

    // Binder描述符仙畦,唯一標(biāo)識符
    private static final String DESCRIPTOR = "com.github.onlynight.aidl_demo2.aidl.IDataManager2";

    // 每個(gè)方法對應(yīng)的ID
    private static final int TRANSACTION_getDataCount = IBinder.FIRST_CALL_TRANSACTION;
    private static final int TRANSACTION_getData = IBinder.FIRST_CALL_TRANSACTION + 1;

    public DataManagerNative() {
        attachInterface(this, DESCRIPTOR);
    }

    /**
     * 將Binder轉(zhuǎn)化為IInterface接口
     *
     * @param binder
     * @return
     */
    public static IDataManager2 asInterface(IBinder binder) {
        if (binder == null) {
            return null;
        }
        //同一進(jìn)程內(nèi)直接返回
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        if ((iin != null) && (iin instanceof IDataManager2)) {
            return (IDataManager2) iin;
        }

        //不在同一進(jìn)程使用代理獲取遠(yuǎn)程服務(wù)
        return new Proxy(binder);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    /**
     * 我們查看Binder的源碼就可以看出實(shí)際上transact方法真正的執(zhí)行體
     * 是這個(gè)onTransact方法。
     *
     * @param code  服務(wù)器回掉的方法ID音婶,每一個(gè)方法都有一個(gè)唯一id慨畸,
     *              這樣方法回調(diào)時(shí)可通過id判斷回調(diào)的方法。
     * @param data  輸入的參數(shù)衣式,傳遞給服務(wù)端的參數(shù)
     * @param reply 輸出的參數(shù)先口,服務(wù)器返回的數(shù)據(jù)
     * @param flags 默認(rèn)傳入0
     * @return
     * @throws RemoteException 遠(yuǎn)端服務(wù)器無響應(yīng)拋出該錯(cuò)誤型奥。
     */
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case TRANSACTION_getDataCount: {
                data.enforceInterface(DESCRIPTOR);
                int _result = this.getDataCount();
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
            case TRANSACTION_getData: {
                data.enforceInterface(DESCRIPTOR);
                List<Data2> _result = this.getData();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    /**
     * 代理類,調(diào)用transact方法碉京。
     */
    private static class Proxy implements IDataManager2 {

        private IBinder remote;

        Proxy(IBinder remote) {
            this.remote = remote;
        }

        public String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public int getDataCount() throws RemoteException {
            // 輸入?yún)?shù)
            Parcel _data = Parcel.obtain();

            //輸出參數(shù)
            Parcel _reply = Parcel.obtain();
            int _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                remote.transact(TRANSACTION_getDataCount, _data, _reply, 0);
                _reply.readException();
                _result = _reply.readInt();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public List<Data2> getData() throws RemoteException {
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            List<Data2> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                remote.transact(TRANSACTION_getData, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(Data2.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public IBinder asBinder() {
            return remote;
        }
    }
}

參考博客:
1. binder守護(hù)進(jìn)程servicemanager簡介
2. Android Binder機(jī)制(一) Binder的設(shè)計(jì)和框架
3. 一篇文章了解相見恨晚的 Android Binder 進(jìn)程間通訊機(jī)制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厢汹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谐宙,更是在濱河造成了極大的恐慌烫葬,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凡蜻,死亡現(xiàn)場離奇詭異搭综,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)划栓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門兑巾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忠荞,你說我怎么就攤上這事蒋歌。” “怎么了委煤?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵堂油,是天一觀的道長。 經(jīng)常有香客問我碧绞,道長府框,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任讥邻,我火速辦了婚禮迫靖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兴使。我一直安慰自己袜香,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布鲫惶。 她就那樣靜靜地躺著,像睡著了一般实抡。 火紅的嫁衣襯著肌膚如雪欠母。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天吆寨,我揣著相機(jī)與錄音赏淌,去河邊找鬼。 笑死啄清,一個(gè)胖子當(dāng)著我的面吹牛六水,可吹牛的內(nèi)容都是我干的俺孙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼掷贾,長吁一口氣:“原來是場噩夢啊……” “哼睛榄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起想帅,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤场靴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后港准,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旨剥,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年浅缸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轨帜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衩椒,死狀恐怖蚌父,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烟具,我是刑警寧澤梢什,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站朝聋,受9級特大地震影響嗡午,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冀痕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一荔睹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧言蛇,春花似錦僻他、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至婿斥,卻和暖如春劝篷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背民宿。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工娇妓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人活鹰。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓哈恰,卻偏偏與公主長得像只估,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子着绷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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