IPC(二)Binder初探

Binder

討論到Binder相關(guān)知識(shí)應(yīng)該三天三夜也討論不完,簡(jiǎn)書的 隔壁老李頭 大佬,花了15篇博客,重頭到位系統(tǒng)的介紹了IPC的過程,涉及Linux基礎(chǔ),JNI等先關(guān)知識(shí)也羅列一通,但本文只面向初學(xué)者,所以文章得從基本用法開始.

簡(jiǎn)介

Binder是安卓的一個(gè)類,翻譯成中文被稱作“粘合劑”,他實(shí)現(xiàn)了IBinder的接口

  • 從IPC的角度來(lái)看Binder是安卓的一個(gè)跨進(jìn)程的方式 ,深層次的講你可以把他理解為一個(gè)虛擬的物理設(shè)備(下一章 的Binder驅(qū)動(dòng)),改設(shè)備的驅(qū)動(dòng)是/dev/binder,
  • 對(duì)于Framwork層,Binder是ServceMananger連接各各Mananger的ManangerService的橋梁。
  • 在應(yīng)用層來(lái)說(shuō)Binder是客戶端與服務(wù)端的進(jìn)行通信的媒介,當(dāng)服務(wù)端實(shí)現(xiàn)bindService方法的時(shí)候,要求返回一個(gè)Binder對(duì)象,客戶端可以通過該對(duì)象來(lái)獲服務(wù) 提供的數(shù)據(jù)服務(wù)(包括普通的服務(wù)或者基于AIDL的服務(wù))

每層比較完后 ,我們?cè)賮?lái)討論一下Binder到底是什么?

Binder 采用的是面向?qū)ο蟮乃枷耄沂呛艿湫偷拿嫦驅(qū)ο蟮乃枷?,?strong>Binder模型(下一章會(huì)將)的四個(gè)角色里,他們都代表BBBBBBINDERRR,對(duì)于使用者而言,eg:Serve端 和 Client 端口 都持有Binder對(duì)象你不會(huì)發(fā)現(xiàn)他們有什么不同,一個(gè)Binder就代表了所有,我們根本不用關(guān)心他的具體的實(shí)現(xiàn)過程或者細(xì)節(jié)??赡墁F(xiàn)在感受不到,接著往下看吧:
創(chuàng)建aidl文件,然后reBuild可以生成以下java文件,你可能會(huì)問aidl是什么?

aidl:安卓接口定義語(yǔ)言,它是實(shí)現(xiàn)IPC一個(gè)很重要的方式,并且底層基于binder。


然后寫入跨進(jìn)程的接口:

// IBookMananger.aidl
package com.example.lixiongjie.ipc;
import com.example.lixiongjie.ipc.Book;//手動(dòng)導(dǎo)入的

// Declare any non-default types here with import statements
interface IBookMananger {
    List<Book> getBookList();
    void addBook(in Book book);


}

這里值得注意的是,實(shí)現(xiàn)接口需要導(dǎo)入其他的類的時(shí)候(除了基本變量以外的成員屬性的注意以下問題:

  • 變量必須進(jìn)行序列化,而且Binder支持的是Parcelable序列化。
  • 創(chuàng)建該成員的AIDL文件聲明
  • 手動(dòng)導(dǎo)入該類(例如文中的Book類)

接著就像上文我們所說(shuō)的reBuild生成:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/lixiongjie/Desktop/Ipc/app/src/main/aidl/com/example/lixiongjie/ipc/IBookMananger.aidl
 */
package com.example.lixiongjie.ipc;
// Declare any non-default types here with import statements

public interface IBookMananger extends android.os.IInterface
{
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.example.lixiongjie.ipc.IBookMananger
    {
        //Binder的唯一標(biāo)識(shí),一般用類名表示。
        private static final java.lang.String DESCRIPTOR = "com.example.lixiongjie.ipc.IBookMananger";
        /** Construct the stub at attach it to the interface. */
        public Stub()
        {
            this.attachInterface(this, DESCRIPTOR);
        }
        /**
         * Cast an IBinder object into an com.example.lixiongjie.ipc.IBookMananger interface,
         * generating a proxy if needed.
         */

        //用于將服務(wù)端的Binder對(duì)象轉(zhuǎn)換為客戶端所需的AIDL接口類型的對(duì)象,這個(gè)轉(zhuǎn)換是有區(qū)別的,如果客戶端與服務(wù)端統(tǒng)一進(jìn)程返回的就是Stub對(duì)象本身,否則就返回封裝后的Stub.proxy.
        public static com.example.lixiongjie.ipc.IBookMananger asInterface(android.os.IBinder obj)
        {
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//查詢是不是本地的,如果是就直接返回Stub對(duì)象本身。
            if (((iin!=null)&&(iin instanceof com.example.lixiongjie.ipc.IBookMananger))) {
                return ((com.example.lixiongjie.ipc.IBookMananger)iin);
            }
            return new com.example.lixiongjie.ipc.IBookMananger.Stub.Proxy(obj);//否則證明不是本地的,傳入遠(yuǎn)程代理過來(lái)。
        }
        //返回當(dāng)前Binder對(duì)象
        @Override
        public android.os.IBinder asBinder()
        {
            return this;
        }
        /**
        *這個(gè)方法比較復(fù)雜,一般運(yùn)行在遠(yuǎn)程端中的Binder線程池中,當(dāng)客戶端發(fā)送跨進(jìn)程請(qǐng)求后遠(yuǎn)程,然后遠(yuǎn)程端會(huì)通過系統(tǒng)底層的分裝來(lái)把遠(yuǎn)程請(qǐng)求交給此方法來(lái)處理,然后來(lái)看看方法內(nèi)部的實(shí)現(xiàn)吧:
        * 首先根據(jù)code確定目標(biāo)方法,然后通過data取出目標(biāo)方法參數(shù),執(zhí)行完后向reply寫入返回值
        *@param code  可以確定客戶端的請(qǐng)求的目標(biāo)方法是啥。
        * @param data  取出目標(biāo)方法的參數(shù)
        * @return  如果為true代表遠(yuǎn)程請(qǐng)求成功,否則就為失敗,可以利用這個(gè)特性來(lái)做權(quán)限驗(yàn)證。
        */
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
            switch (code)
            {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList:
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.example.lixiongjie.ipc.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook:
                {
                    data.enforceInterface(DESCRIPTOR);
                    com.example.lixiongjie.ipc.Book _arg0;
                    if ((0!=data.readInt())) {
                        _arg0 = com.example.lixiongjie.ipc.Book.CREATOR.createFromParcel(data);
                    }
                    else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
        
        private static class Proxy implements com.example.lixiongjie.ipc.IBookMananger
        {
            private android.os.IBinder mRemote;
            Proxy(android.os.IBinder remote)
            {
                mRemote = remote;
            }
            @Override public android.os.IBinder asBinder()
            {
                return mRemote;
            }
            public java.lang.String getInterfaceDescriptor()
            {
                return DESCRIPTOR;
            }
            
             /**
     * 1.創(chuàng)建兩個(gè)Parcel對(duì)象1.data傳入的對(duì)象2.reply輸出的對(duì)象 還要返回對(duì)象reslut:List;
     * 2.調(diào)用transact方法發(fā)起RPC(遠(yuǎn)程過程調(diào)用)請(qǐng)求,同時(shí)線程掛起
     * 3.然后服務(wù)端的onTranscat會(huì)調(diào)用,直到RPC返回值,線程才恢復(fù)
     * 4.最后從reply中取出PRC的結(jié)果,返回得到的值
     * @return
     * @throws android.os.RemoteException
     */
            @Override 
            public java.util.List<com.example.lixiongjie.ipc.Book> getBookList() throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.example.lixiongjie.ipc.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.example.lixiongjie.ipc.Book.CREATOR);
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
            
            
     /**
     * 和上面一樣,但是值得注意的是addBook沒有返回值,所以他不需要reply,但是在傳參進(jìn)去會(huì)調(diào)用writeToParcel()傳入序列化的對(duì)象Parcel中
     * @param book
     * @throws android.os.RemoteException
     */
            @Override 
            public void addBook(com.example.lixiongjie.ipc.Book book) throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book!=null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    }
                    else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    public java.util.List<com.example.lixiongjie.ipc.Book> getBookList() throws android.os.RemoteException;
    public void addBook(com.example.lixiongjie.ipc.Book book) throws android.os.RemoteException;
}

由于客戶端進(jìn)行PRC的時(shí)候,客戶端的線程會(huì)掛起等待服務(wù)端的進(jìn)程返回?cái)?shù)值,但是如果這個(gè)遠(yuǎn)程方法很耗時(shí),那就不能再UI線程發(fā)起次遠(yuǎn)程請(qǐng)求了,其次服務(wù)端的Binder運(yùn)行在Binder的線程池中,所以不管耗不耗時(shí)間都應(yīng)該采用同步方式去處理,因?yàn)樗鼈兌荚谝粋€(gè)線程中。

手動(dòng)寫B(tài)inder在服務(wù)端需要?jiǎng)?chuàng)建一個(gè)BookManangerImpl并在Service得到onBind方法返回即可。

上述代碼中我們查看結(jié)構(gòu),第一個(gè)該接口實(shí)現(xiàn)是 android.os.IInterface接口,這里的IInterface代表的就是遠(yuǎn)程server對(duì)象具有什么能力。

抽象類Stub extends android.os.Binder implements com.example.lixiongjie.ipc.IBookMananger
這個(gè)類繼承了Binder, 說(shuō)明它是一個(gè)Binder本地對(duì)象,它實(shí)現(xiàn)了IInterface接口,表明它具有遠(yuǎn)程Server承諾給Client的能力,至于為什么是抽象類,是因?yàn)樵O(shè)定的方法要實(shí)現(xiàn)的子類是服務(wù)端,及在接口中遠(yuǎn)程端實(shí)現(xiàn)的方法。

DESCRIPTOR:是該Binder的唯一標(biāo)識(shí),會(huì)發(fā)現(xiàn)我們?cè)?onTransact 函數(shù)中的返回?cái)?shù)據(jù)體reply,和add方法里寫入數(shù)據(jù)的時(shí)候都會(huì)加上這個(gè)標(biāo)識(shí),驗(yàn)證就在onTransact的case中驗(yàn)證。

in、out、inout

在官方文檔中支出,所有的非原語(yǔ)參數(shù)需要指示數(shù)據(jù)的方向標(biāo)記,可以是in、out、inout。默認(rèn)的原語(yǔ)是in,不能是其他流向。
這里指定的非原語(yǔ)是指:除了Java的基本類型外的其他參數(shù),也就是對(duì)象。我們?cè)贏IDL使用的時(shí)候需要知道這個(gè)參數(shù)的流向。
那什么是數(shù)據(jù)的方向標(biāo)記呢?
首先,數(shù)據(jù)的方向標(biāo)記是針對(duì)客戶端中的那個(gè)傳入的方法參數(shù)而言。數(shù)據(jù)流向的標(biāo)識(shí)符不能使用在返回參數(shù)上,只能使用在方法參數(shù)上面。

in:他表示的是這個(gè)參數(shù)只能從客戶端流向服務(wù)端,比如客戶端傳遞了一個(gè)User對(duì)象給服務(wù)端,服務(wù)端會(huì)收到一個(gè)完整的User對(duì)象,然后假如在服務(wù)端對(duì)這個(gè)對(duì)象進(jìn)行操作,那么這個(gè)改變是不會(huì)反映到客戶端的,這個(gè)流向也就是只能從客戶端到服務(wù)端。

out:他表示,當(dāng)客戶端傳遞參數(shù)到服務(wù)端的時(shí)候,服務(wù)端將會(huì)收到一個(gè)空的對(duì)象,假如服務(wù)端對(duì)該對(duì)象進(jìn)行操作,將會(huì)反映到客戶端。比如,客戶端傳遞一個(gè)User對(duì)象到服務(wù)端,服務(wù)端接收到的是一個(gè)空的User對(duì)象(不是null,只是有點(diǎn)像new一個(gè)User對(duì)象)。當(dāng)服務(wù)端對(duì)這個(gè)User對(duì)象進(jìn)行改變的時(shí)候,他的值變化將會(huì)反映到客戶端。

inout,它具有這二者的功能,也就是客戶端傳遞對(duì)象到服務(wù)端,可以接收到完整的對(duì)象,同時(shí)服務(wù)端改變對(duì)象,也會(huì)反映到客戶端。
總結(jié)來(lái)說(shuō),in類似于傳值,out類似于傳引用,只是out的引用 到了服務(wù)端為空,inout則具有二者的功能,默認(rèn)的是in。

linkToDeath和unlinkToDeath

如果遠(yuǎn)程端因?yàn)槟承┰蚪K止了連接,這樣會(huì)導(dǎo)致我們遠(yuǎn)程調(diào)用失敗,但是關(guān)鍵的地方是我們并不知道Binder的連接斷開,這樣會(huì)導(dǎo)致客戶端受影響。
然后英雄linkToDeath和unlinkToDeath登場(chǎng),通過linkToDeath去設(shè)置死亡代理,當(dāng)監(jiān)聽到死亡的時(shí)候,我們會(huì)受到通知,然后進(jìn)行下一步處理工作。

首先聲明DeathRecipient對(duì)象,該接口只有一個(gè)方法binderDied,當(dāng)Binder死亡的時(shí)候會(huì)回到該方法,然后我們可以移出之前重寫綁定遠(yuǎn)程服務(wù)。

Bundle

四大組件傳輸?shù)娜蠼M件(除了ContentPrider),都是都支持在Intent的中傳輸Bundle數(shù)據(jù),由于Bundle實(shí)現(xiàn)了Parcelable接口,所以在組件間支持跨進(jìn)程傳輸也理所應(yīng)當(dāng),當(dāng)然傳輸?shù)臄?shù)據(jù)也應(yīng)該被序列化,eg:基本類型,實(shí)現(xiàn)parcelable接口對(duì)象,實(shí)現(xiàn)了SerialVersion接口的對(duì)象,和Android特殊的對(duì)象。

除了直接傳輸?shù)牡湫蛨?chǎng)景

見《藝術(shù)探索》62※

使用文件共享

共享文件是一個(gè)不錯(cuò)的進(jìn)程通信方式,在Window上,一個(gè)文件加上排斥鎖來(lái)解決線程讀寫的問題,但是Android是基于Linux ,使得讀寫的并發(fā)可以沒有限制的進(jìn)行,所以兩個(gè)線程對(duì)同一個(gè)文件進(jìn)行操作被允許了,但是有可能會(huì)出現(xiàn)問題,當(dāng)然我們還是可以, 通過序列化一個(gè)對(duì)象在系統(tǒng)文件中然后另外個(gè)進(jìn)程在中恢復(fù)這個(gè)對(duì)象,

//在第一進(jìn)程的Activity儲(chǔ)存
 private void persistToFile(){
        new Thread(new Runnable() {
            @Override
            public void run() {
             Book book = new Book(1,"童話故事");
             File file = new File();
             if (!file.exists()){
                 file.mkdirs();
             }
             File cache = new File();
                ObjectOutputStream out = null;
                try {
                    out = new ObjectOutputStream(new FileOutputStream(cache));
                    out.writeObject(book);

                } catch (IOException e) {
                    e.printStackTrace();
                }
                finally {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
    
    //在第二進(jìn)程的Activity讀取
     private void recoverFromFile(){
        new  Thread(new Runnable() {
            @Override
            public void run() {
                Book book = null;
                File cache = new File();
                if (cache.exists()){
                    ObjectInputStream inputStream =null;
                    try {
                        inputStream = new ObjectInputStream(new  FileInputStream(""));
                        book = (Book) inputStream.readObject();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    finally {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }
            }
        }).start();
    }

可以看到這個(gè)其實(shí)就是之前的內(nèi)容,但是我得注意的是儲(chǔ)存的格式可以是文本文件,也可以是XML文件,但是我們得讓讀取的雙方約定格式,第二份存在的問題就是并發(fā)讀寫的問題,如果是并發(fā)讀有可能拿不到最新的,但是并發(fā)寫問題就更為嚴(yán)重,所以要么避免要么得同步來(lái)限制多線程的寫。

SharedPrefercens

SharedPrefercens是輕量級(jí)儲(chǔ)存方案,它通過鍵值對(duì)來(lái)儲(chǔ)存數(shù)據(jù),底層實(shí)現(xiàn)XML文件來(lái)存儲(chǔ)鍵值對(duì),目錄位于/data/data/packagename(包名)/shared_prefs下,本質(zhì)上講SP是文件一種,但是系統(tǒng)對(duì)它的讀寫有緩存策略,導(dǎo)致內(nèi)存有一份SP文件備份緩存,因此在多線程的模式下,讀寫會(huì)不可靠

最后編輯于
?著作權(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)容