Android IPC機制(三):淺談Binder的使用

playniuniu 8年前發布 | 32K 次閱讀 ipc Android開發 移動開發 Android

一、前言

        在上一篇文章Android IPC機制(二):AIDL的基本使用方法中,筆者講述了安卓進程間通訊的一個主要方式,利用AIDL進行通訊,并介紹了AIDL的基本使用方法。其實AIDL方式利用了Binder來進行跨進程通訊,Binder是Android中的一種跨進程通訊方式,其底層實現原理比較復雜,限于筆者水平,不能展開詳談,所以這篇文章主要談談以AIDL為例,談談Binder的使用方法。

二、原理

        上一篇文章中創建了一個IMyAidl.aidl文件,即接口文件,隨即編譯了該文件,生成了一個.java文件,該文件在gen目錄下:

IMyAidl.java所在路徑


        打開該文件,得到如下代碼:

* This file is auto-generated.  DO NOT MODIFY. 
 * Original file: G:\\Android\\Project\\MyAidl\\app\\src\\main\\aidl\\com\\chenyu\\service\\IMyAidl.aidl 
 */  
package com.chenyu.service;  

public interface IMyAidl extends android.os.IInterface {  
    /** 
     * Local-side IPC implementation stub class. 
     */  
    public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {  
        ......  

    public void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException;  

    public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException;</span>  
}

        其中省略了一部分,我們先從大體上認識,然后在深入。
(1)從大體上看,該java文件是一個接口,繼承了IInterface接口,接著,聲明了一個靜態內部抽象類:Stub,然后是兩個方法,可以看到,這兩個方法分別是原IMyAidl.aidl文件內聲明的兩個方法。
(2)我們看回Stub類,它繼承了Binder,同時實現了IMyAidl。這個類實現了自己的接口!那么可想而知,該接口所聲明的addPerson,getPersonList方法,將會在Stub類得到實現,具體如何實現,我們展開Stub類:

public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {  
        private static final java.lang.String DESCRIPTOR = "com.chenyu.service.IMyAidl";  

        /** 
         * Construct the stub at attach it to the interface. 
         */  
        public Stub() {<span style="white-space:pre">     </span>// 1  
            this.attachInterface(this, DESCRIPTOR);  
        }  

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

        @Override  
        public android.os.IBinder asBinder() {<span style="white-space:pre">      </span>// 3  
            return this;  
        }  

        @Override  
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {<span style="white-space:pre">             </span>// 4  
            switch (code) {  
                case INTERFACE_TRANSACTION: {  
                    reply.writeString(DESCRIPTOR);  
                    return true;  
                }  
                case TRANSACTION_addPerson: {  
                    data.enforceInterface(DESCRIPTOR);  
                    com.chenyu.service.Person _arg0;  
                    if ((0 != data.readInt())) {  
                        _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);  
                    } else {  
                        _arg0 = null;  
                    }  
                    this.addPerson(_arg0);  
                    reply.writeNoException();  
                    return true;  
                }  
                case TRANSACTION_getPersonList: {  
                    data.enforceInterface(DESCRIPTOR);  
                    java.util.List<com.chenyu.service.Person> _result = this.getPersonList();  
                    reply.writeNoException();  
                    reply.writeTypedList(_result);  
                    return true;  
                }  
            }  
            return super.onTransact(code, data, reply, flags);  
        }  

        private static class Proxy implements com.chenyu.service.IMyAidl {<span style="white-space:pre">  </span>// 5  
            ...  
        }  

        static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); // 6  
        static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);  
    }

(3)從上往下地,我們逐個分析一下各個方法或者變量的作用:
①Stub()構造方法:此方法調用了父類Binder的attachInterface()方法,將當前的Interface與Binder聯系起來,由于傳遞了DESCRIPTOR這個參數,唯一標識了當前Interface。

②asInterface(IBinder obj) :靜態方法,傳遞了一個接口對象,該對象從哪里傳遞進來的呢?我們來看看上一章博客的客戶端代碼:

public void onServiceConnected(ComponentName name, IBinder service) {  
            Log.d("cylog", "onServiceConnected success");  
            iMyAidl=IMyAidl.Stub.asInterface(service);  

        }

在這里,可以看到,調用了IMyAidl.Stub.asInterface(service)方法,即上面的②號方法,并且把service傳遞了進去,我們接著往下看:

public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) {  
            if ((obj == null)) {  
                return null;  
            }  
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);  
            if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {  
                return ((com.chenyu.service.IMyAidl) iin);  
            }  
            return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);  
        }

首先判斷obj是否有效,如果無效直接返回Null,說明客戶端與服務端的連接失敗了。接著調用了obj.queryLocalInterface(DESCRIPTOR)方法,為IInterface的對象賦值,注意到這里再次傳遞了DESCRIPTOR參數,可以猜測,這個方法應該是查找與當前Interface相關的一個方法,我們看看IBinder接口的queryLocalInterface()方法:

/** 
     * Attempt to retrieve a local implementation of an interface 
     * for this Binder object.  If null is returned, you will need 
     * to instantiate a proxy class to marshall calls through 
     * the transact() method. 
     */  
    public IInterface queryLocalInterface(String descriptor);

大概意思是說,根據descriptor的值,試圖為Binder取回一個本地的interface,其中local意思應為當前進程,如果返回值是null,那么應該實例化一個proxy類。在了解了obj.queryLocalInterface(DESCRIPTOR)方法后,我們再次回到asInterface(obj)方法,繼續往下看:接著是一個if判斷,主要判斷客戶端與服務端是否處于同一進程,如果處于同一進程,那么直接返回了Stub對象本身,如果不是同一個進程,那么就會新建一個Proxy代理類(下面會提到)。

③asBinder():此方法用于返回當前對象本身。

④onTransact(int code,Parcel data,Parcel reply,int flags):該方法一般運行在服務端中的Binder線程池中,即遠程請求會在該方法得到處理。傳遞的code值用于判斷客戶端的請求目標,是addPerson或者是getPersonList。我們以請求目標為addPerson()為例分析一下,提取其主要函數體如下:

case TRANSACTION_addPerson: {  
                    data.enforceInterface(DESCRIPTOR);  
                    com.chenyu.service.Person _arg0;  
                    if ((0 != data.readInt())) {  
                        _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);  
                    } else {  
                        _arg0 = null;  
                    }  
                    this.addPerson(_arg0);  
                    reply.writeNoException();  
                    return true;  
                }

首先聲明了_arg0是Person類的對象,接著,以data為參數調用了Person類的CREATOR.createFromParcel方法反序列化生成了Person類,這也是為什么實現了Parcelable接口的類應該同時實現CREATOR,原來在這里調用了反序列化的方法。接著,調用this.addPerson(_arg0)方法,注意:這里的this代表當前的Binder對象,那么由Binder調用的addPerson(_arg0)方法,實際上是由綁定到Binder的service調用的,即服務端調用了自身的addPerson方法。為了方便明白,讓我們來回顧一下上一篇文章服務端的代碼:

private IBinder iBinder= new IMyAidl.Stub() {  
        @Override  
        public void addPerson(Person person) throws RemoteException {  
            persons.add(person);  
        }  

        @Override  
        public List<Person> getPersonList() throws RemoteException {  
            return persons;  
        }  
    };

是不是一下子就明白了?IMyAidl.Stub()實現的接口,其中的方法在服務端得到了實現:addPerson和getPersonList()。當在Binder線程池中,調用了this.addPerson()方法,實際上回調了服務端的addPerson方法,而底層到底是怎么實現的,限于筆者的水平,暫時不了解,等以后筆者再深入了解Binder的工作機制再回答這個問題。

好了,回到當前的類,我們繼續往下看:
⑤private static class Proxy:這里又出現了一個私有的靜態內部類,關于這個類將在接下來詳細講述。

⑥最后兩行代碼分別是兩個常量,標志了兩個方法,即上面提到的code值。

(4)Proxy類,也實現了IMyAidl接口,同時實現了addPerson和getPersonList的方法。而Proxy類在哪里被實例化的呢?是上面(3)②中,當客戶端與服務端不在同一個進程的時候,就會實例化這個代理類,并返回給客戶端。什么叫做代理類呢?所謂代理,即一個中介,客戶端拿到的實例,能操作服務端的部分功能,讓客戶端以為自己已經拿到了服務端的實例,其實不是,只是拿到服務端的一個代理而已。接下來我們展開該類,看看內部:

private static class Proxy implements com.chenyu.service.IMyAidl {  
            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;  
            }  

            @Override  
            public void addPerson(com.chenyu.service.Person person) 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 ((person != null)) {  
                        _data.writeInt(1);  
                        person.writeToParcel(_data, 0);  
                    } else {  
                        _data.writeInt(0);  
                    }  
                    mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);  
                    _reply.readException();  
                } finally {  
                    _reply.recycle();  
                    _data.recycle();  
                }  
            }  

            @Override  
            public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException {  
                android.os.Parcel _data = android.os.Parcel.obtain();  
                android.os.Parcel _reply = android.os.Parcel.obtain();  
                java.util.List<com.chenyu.service.Person> _result;  
                try {  
                    _data.writeInterfaceToken(DESCRIPTOR);  
                    mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);<span style="white-space:pre"> </span>   //①  
                    _reply.readException();  
                    _result = _reply.createTypedArrayList(com.chenyu.service.Person.CREATOR);  
                } finally {  
                    _reply.recycle();  
                    _data.recycle();  
                }  
                return _result;  
            }  
        }

這里關注兩個接口方法的實現:addPerson和getPersonList,這兩個方法已經多次出現了,而在代理類實現的這兩個方法,是運行在客戶端的!!!其主要實現過程是這樣的:當客戶端拿到代理類,調用addPerson或者getPersonList方法,首先會創建輸入型Parcel對象_data和輸出型Parcel對象_reply,接著調用①號代碼調用transact來發起RPC遠程請求,同時當前線程會被掛起,此時,服務端的onTransact會被調用,即上面所說的(3)④號代碼,當服務端處理完請求后,會返回數據,當前線程繼續執行知道返回 _result結果。

至此,對于IPC的方式之一——AIDL的原理已經剖析完畢,接下來總結一下:

1、客戶端發出綁定請求,服務端返回一個Binder對象,該對象能處理跨進程請求,而客戶端拿到的是Binder對象的引用,Binder的實體是在服務端的。客戶端執行asInterface()方法,如果客戶端和服務端處于同一進程,則直接返回服務端的Stub對象本身,如果處于不同進程,則返回的是Stub.proxy代理類對象。
2、客戶端發送遠程請求(addPerson或者getPersonList),此時客戶端線程掛起,Binder拿到數據后,對數據進行處理如在不同進程,會把數據寫入Parcel,調用Transact方法。
3、觸發onTransact方法,該方法運行在Binder線程池,方法中會調用到服務端實現的接口方法,當數據處理完畢后,返回reply值,經過Binder返回客戶端,此時客戶端線程被喚醒。

三、優化

最后說一說如何優化AIDL,上面提到,客戶端發送請求后,會被掛起,這意味著,如果處理數據的時間過長,那么該線程就一直等不到喚醒,這是很嚴重的,如果在UI線程發送請求,會直接導致ANR,所以我們需要在子線程發送異步請求,這樣才能避免ANR。還有一點,Binder可能是意外死亡的,如果Binder意外死亡,那么子線程可能會一直掛起,所以我們要啟用重新連接服務。有兩個方法,一個是給Binder設置DeathRecipient監聽,另一個是在onServiceDisconnected中重連服務。

來自: http://www.jianshu.com/p/b96713fc4e5e

 本文由用戶 playniuniu 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!