Android 開發(fā)藝術(shù)探索筆記(四) 之 Binder 及AIDL

1.Binder 介紹

從 Android 層面來說及汉,Binder 就是客戶端和服務(wù)端進(jìn)行通信的媒介山林,當(dāng) bindService 的時(shí)候银受,服務(wù)端會(huì)返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的 Binder 對(duì)象奋岁,通過這個(gè) Binder 對(duì)象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù)环础,這里的服務(wù)包括普通服務(wù)和基于 AIDL 的服務(wù)旨指。Binder 主要用于 Service,包括 AIDL 和 Messenger喳整,其實(shí) Messenger 的底層是 AIDL

2.使用 AIDL

這里主要介紹一下思路裸扶,具體參考一下 Android:學(xué)習(xí)AIDL框都,這一篇文章就夠了(上) 進(jìn)行項(xiàng)目搭建。

  • 一呵晨、新建服務(wù)端工程

    服務(wù)端代碼:https://github.com/innovatorCL/IPCServer

  • 1.新建 aidl 包魏保,客戶端和服務(wù)器的包名必須一樣。

  • 2.新建非基本數(shù)據(jù)類型的數(shù)據(jù)文件摸屠,包括 xx.java 和 xx.aidl 文件谓罗,同時(shí)在 Module 的 gradle 文件中聲明源碼路徑包含 aidl,不然會(huì)報(bào)找不到文件的錯(cuò)誤季二。

    sourceSets {
         main {
             java.srcDirs = ['src/main/java', 'src/main/aidl']
         }
     }
    
    AIDL包
  • 3.新建接口方法 xxx.aidl 文件檩咱。所有需要被客戶端調(diào)用的方法在這個(gè)文件中聲明揭措。

    AIDL 定義接口
  • 4.新建一個(gè) Service 作為服務(wù)端,在里面是實(shí)現(xiàn)接口 xxx.aidl 聲明的方法刻蚯,并實(shí)例化成為一個(gè) XXX 對(duì)象绊含,在客戶端 bind 服務(wù)端的時(shí)候返回給客戶端(其實(shí)返回給客戶端的是 Android 生成的 XXX.java 里面的內(nèi)部代理類對(duì)象,XXX.Stub.Proxy炊汹,客戶端通過這個(gè)對(duì)象調(diào)用服務(wù)端 Service 的方法躬充。)

 /**
 *
 * 服務(wù)端的AIDLService.java
 *
 * 在服務(wù)端實(shí)現(xiàn)AIDL中定義的方法接口的具體邏輯,
 * 然后在客戶端調(diào)用這些方法接口讨便,從而達(dá)到跨進(jìn)程通信的目的充甚。
 * Created by innovator on 2018/1/18.
 */

public class AIDLService extends Service{


    //包含Book的List
    private List<Book> mBooks = new ArrayList<>();


    //由AIDL生成的BookManager類的代理類
    private final BookManager.Stub mBookManager = new BookManager.Stub() {
        @Override
        public List<Book> getBooks() throws RemoteException {
            synchronized (this){
                Log.i("TAG","服務(wù)端發(fā)送數(shù)據(jù)給客戶端");
                if(mBooks != null){
                    return mBooks;
                }

                return new ArrayList<>();
            }
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Log.i("TAG","服務(wù)端接收客戶端的數(shù)據(jù)");
            synchronized (this) {
                if (mBooks == null) {
                    mBooks = new ArrayList<>();
                }
                if (book == null) {
                    Log.e("TAG", "客戶端傳了一個(gè)空的Book對(duì)象");
                    book = new Book();
                }
                //因?yàn)?getBook 的參數(shù)的 tag 是 inout,所以服務(wù)端修改了book的參數(shù)霸褒,客戶端的應(yīng)該也會(huì)修改
                book.setPrice(6666);
                if (!mBooks.contains(book)) {
                    mBooks.add(book);
                }
                //打印mBooks列表伴找,觀察客戶端傳過來的值
                Log.e("TAG", "服務(wù)端的 addBooks() 方法被調(diào)用 , 打印服務(wù)端收到的數(shù)據(jù) : " + mBooks.toString());
            }
        }
    };

    @Override
    public void onCreate() {
        Book book = new Book();
        book.setName("在復(fù)雜的世界做個(gè)明白人");
        book.setPrice(2222);
        mBooks.add(book);
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e("TAG", String.format("服務(wù)端連接上了客戶端,連接的 ntent 是 %s", intent.toString()));
        return mBookManager;
    }
}

在 Manifest.xml 中聲明該 Service:

    <service
        android:name=".Service.AIDLService"
        android:exported="true">
        <intent-filter>
            <action android:name="com.innovator.aidl"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
    </service>

  • 二、新建客戶端工程

    客戶端代碼:https://github.com/innovatorCL/IPCClient

    • 1.新建和服務(wù)端同名的 aidl 包傲霸,將服務(wù)端的 aidl 文件全部 copy 過去疆瑰。

      AIDL 定義接口
    • 2.在 MainActivity 中連接服務(wù)端 Service ,并獲取服務(wù)端返回的代理對(duì)象昙啄,調(diào)用服務(wù)端的方法傳入或者讀取服務(wù)端的數(shù)據(jù)穆役。

/**
 *
 * IPC 客戶端的 MainActivity.java
 */
public class MainActivity extends Activity {

    //由AIDL文件生成的Java類
    private BookManager mBookManager;

    //標(biāo)志當(dāng)前與服務(wù)端連接狀況的布爾值,false為未連接梳凛,true為連接中
    private boolean mBound;

    //包含Book對(duì)象的list
    private List<Book> mBooks;

    private Book book;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }


    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i("TAG","連接上了服務(wù)端");
            mBookManager = BookManager.Stub.asInterface(service);
            mBound = true;

            //獲取代理對(duì)象耿币,調(diào)用服務(wù)端的方法
            if(mBookManager != null){
                try {
                    mBooks = mBookManager.getBooks();
                    Log.i("TAG","獲取到了服務(wù)端數(shù)據(jù):"+mBooks.toString());
                }catch (RemoteException e){
                    e.printStackTrace();
                    Log.i("TAG","調(diào)用服務(wù)端方法出現(xiàn)異常:"+e.getMessage());
                }

            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false;
            Log.i("TAG","斷開服務(wù)端連接");
        }
    };

    /**
     * 按鈕的點(diǎn)擊事件,點(diǎn)擊之后調(diào)用服務(wù)端的addBookIn方法
     *
     * @param v
     */
    public void addBook(View v){
        if(!mBound){
            attemptBindService();
            Toast.makeText(this, "當(dāng)前與服務(wù)端處于未連接狀態(tài)韧拒,正在嘗試重連淹接,請(qǐng)稍后再試", Toast.LENGTH_SHORT).show();
            return;
        }

        if(mBookManager == null){
            return;
        }

        book = new Book();
        book.setName("認(rèn)知突圍");
        book.setPrice(88);
        //調(diào)用服務(wù)器端的方法,傳入一個(gè)Book對(duì)象
        try{
            mBookManager.addBook(book);
            Log.i("TAG","調(diào)用服務(wù)端方法傳入一個(gè)Book");
        }catch (RemoteException e){
            e.printStackTrace();
            Log.i("TAG","調(diào)用服務(wù)端方法出現(xiàn)異常:"+e.getMessage());
        }
    }

    /**
     * 點(diǎn)擊了 addBook 之后看看客戶端的數(shù)據(jù)有沒有同步被改
     * 驗(yàn)證 inout tag 的功能
     *
     * 確實(shí)是同步更改了 inout 傳入的那個(gè)對(duì)象的值
     * @param v
     */
    public void getBooks(View v) {

        Log.i("TAG","打印 addBooks 之后的數(shù)據(jù) :"+book.toString());
    }

    /**
     * 嘗試連接上服務(wù)器
     */
    private void attemptBindService(){
        Intent i = new Intent();
        i.setAction("com.innovator.aidl");
        i.setPackage("com.innovator.ipcserver");
        bindService(i,mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if(!mBound){
            attemptBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if(mBound){
            unbindService(mServiceConnection);
            mBound = false;
        }
    }
}

3. AIDL 工作流程分析

首先介紹幾個(gè)類:

  • Stub : 生成的 AIDL 的內(nèi)部類叛溢,繼承 Binder, 實(shí)現(xiàn)了 IInterface 接口塑悼;

  • Proxy : Stub 的內(nèi)部類,實(shí)現(xiàn)了我們自定義的 AIDL 接口楷掉;

  • BinderProxy : Binder 的內(nèi)部類厢蒜,跨進(jìn)程通信時(shí)傳遞的對(duì)象

  • 流程一:進(jìn)程間通信時(shí),Client 端開啟服務(wù) bindService, Server 端 onBind 方法返回 Stub 對(duì)象烹植,通過 Binder 封裝成 BinderProxy 對(duì)象 返回斑鸦,到 Client 端的 onServiceConnected 方法接收到的就是 BinderProxy 對(duì)象,再調(diào)用 BookManager.Stub.asInterface(service) 封裝成 Proxy, BinderProxy 是它的成員變量草雕。

    到這里巷屿,Client 端已經(jīng)拿到了 Service 端傳遞過來的 Binder 對(duì)象。

  • 流程二:Client 端調(diào)用方法(例:getBookList)時(shí)墩虹,會(huì)先調(diào)用 Proxy.getBookList, 這里會(huì)調(diào)用 mRemote.transact(這個(gè)是在客戶端進(jìn)程做的事), mRemote 也就是 BinderProxy 對(duì)象嘱巾,實(shí)現(xiàn)跨進(jìn)程的操作, BinderProxy 里面封裝了 Stub 對(duì)象憨琳,所以會(huì)再跨進(jìn)程調(diào)用到 Server 端的 Stub.onTransact(這個(gè)是在服務(wù)端的 Binder 線程池做的事), Stub.onTransact 內(nèi)部調(diào)用 Service 里自己實(shí)現(xiàn)的 getBookList 方法,執(zhí)行完后跨進(jìn)程返回到 BinderProxy 中浓冒,BinderProxy 再跨進(jìn)程返回到 Client 端栽渴,到此一次方法調(diào)用結(jié)束。

補(bǔ)充一張進(jìn)程間方法調(diào)用的圖稳懒。

image.png
/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/innovator/AndroidStudioProjects/IPCServer/app/src/main/aidl/com/innovator/ipcclient/BookManager.aidl
 */
package com.innovator.ipcclient;
//定義方法接口

public interface BookManager extends android.os.IInterface {

/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.innovator.ipcclient.BookManager {

    private static final java.lang.String DESCRIPTOR = "com.innovator.ipcclient.BookManager";
    /** Construct the stub at attach it to the interface. */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an com.innovator.ipcclient.BookManager interface,
     * generating a proxy if needed.
     */
    public static com.innovator.ipcclient.BookManager asInterface(android.os.IBinder obj) {
        if ((obj==null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin!=null)&&(iin instanceof com.innovator.ipcclient.BookManager))) {
            return ((com.innovator.ipcclient.BookManager)iin);
        }
        return new com.innovator.ipcclient.BookManager.Stub.Proxy(obj);
    }

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

    @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_getBooks: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<com.innovator.ipcclient.Book> _result = this.getBooks();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);
                com.innovator.ipcclient.Book _arg0;
                if ((0!=data.readInt())) {
                    _arg0 = com.innovator.ipcclient.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                if ((_arg0!=null)) {
                    reply.writeInt(1);
                    _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }


    private static class Proxy implements com.innovator.ipcclient.BookManager {
        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;
        }


        //所有的返回值前都不需要加任何東西闲擦,不管是什么數(shù)據(jù)類型
        @Override
        public java.util.List<com.innovator.ipcclient.Book> getBooks() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.innovator.ipcclient.Book> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.innovator.ipcclient.Book.CREATOR);
            }
            finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }


        //傳參時(shí)除了Java基本類型以及String,CharSequence之外的類型
        //都需要在前面加上定向tag场梆,具體加什么量需而定
        @Override
        public void addBook(com.innovator.ipcclient.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();
                if ((0!=_reply.readInt())) {
                    book.readFromParcel(_reply);
                }
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }



    }

    static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}



    //所有的返回值前都不需要加任何東西墅冷,不管是什么數(shù)據(jù)類型
    public java.util.List<com.innovator.ipcclient.Book> getBooks() throws android.os.RemoteException;


    //傳參時(shí)除了Java基本類型以及String,CharSequence之外的類型
    //都需要在前面加上定向tag或油,具體加什么量需而定
    public void addBook(com.innovator.ipcclient.Book book) throws android.os.RemoteException;
}

詳細(xì)分析參考:

4. IPC 應(yīng)用場(chǎng)景

  • 騰訊系顶岸、阿里系等大廠的 APP 之間的通信
  • 系統(tǒng) APP 和系統(tǒng)服務(wù)之間的通信
  • 推送腔彰、IM
  • 雙進(jìn)程守護(hù)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市辖佣,隨后出現(xiàn)的幾起案子霹抛,更是在濱河造成了極大的恐慌,老刑警劉巖卷谈,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杯拐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡世蔗,警方通過查閱死者的電腦和手機(jī)端逼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來污淋,“玉大人顶滩,你說我怎么就攤上這事〈绫” “怎么了诲祸?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)而昨。 經(jīng)常有香客問我,道長(zhǎng)找田,這世上最難降的妖魔是什么歌憨? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮墩衙,結(jié)果婚禮上务嫡,老公的妹妹穿的比我還像新娘甲抖。我一直安慰自己,他們只是感情好心铃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布准谚。 她就那樣靜靜地躺著,像睡著了一般去扣。 火紅的嫁衣襯著肌膚如雪柱衔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天愉棱,我揣著相機(jī)與錄音唆铐,去河邊找鬼。 笑死奔滑,一個(gè)胖子當(dāng)著我的面吹牛艾岂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朋其,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼王浴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了梅猿?” 一聲冷哼從身側(cè)響起氓辣,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粒没,沒想到半個(gè)月后筛婉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡癞松,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年爽撒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片响蓉。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡硕勿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出枫甲,到底是詐尸還是另有隱情源武,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布想幻,位于F島的核電站粱栖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脏毯。R本人自食惡果不足惜闹究,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望食店。 院中可真熱鬧渣淤,春花似錦赏寇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至用踩,卻和暖如春渠退,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捶箱。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國打工智什, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丁屎。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓荠锭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親晨川。 傳聞我的和親對(duì)象是個(gè)殘疾皇子证九,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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