Binder機制之AIDL

** 簡單說一下,第一次寫文章,有點不習慣,從下定決心看一看Android的系統源碼開始,看過了簡單的系統源碼如何修改編譯,簡單瞭解了點Linux內核驅動的一點點知識,隨後跟着老羅的Android系統源代碼情景分析一書看了看Android的啓動流程以及Activity跳轉,因爲這些知識點都須要對Binder機制的進行了解,所以決定看一看Binder機制,本文從Service跨進程間訪問的角度瞭解,因爲IBinder.Stub使用代理模式,所以本文也對代理模式簡單闡述。(注:如有什麼地方闡述有誤,敬請指正。)html

代理模式

代理模式的定義:代理模式屬於結構型模式,指的是爲其餘對象提供一種代理以控制對這個對象的訪問。在某些狀況下,一個對象不適合或者不能直接引用另外一個對象,而代理對象能夠在客戶端和目標對象之間起到中介的做用。java

應用場景:當Client爲了實現Subject的目標而直接訪問RealSubject存在問題的時候(好比對象建立開銷很大,或者某些操做須要安全控制,或者須要進程外的訪問),就須要Proxy來代替Subject來執行request操做。例如:AOP切面編程 AIDL跨進程間通信等android

代理模式的主要角色以下:c++

  • 抽象(Subject)類:經過接口或抽象類聲明真實主題和代理對象實現的業務方法。
  • 真實(Real Subject)類:實現了抽象主題中的具體業務,是代理對象所表明的真實對象,是最終要引用的對象。
  • 代理(Proxy)類:提供了與真實主題相同的接口,其內部含有對真實主題的引用,它能夠訪問、控制或擴展真實主題的功能。

類圖以下: 編程

代碼示例:安全

/**
 * 代理接口
 */
public interface ProxyInterface {
	public abstract void handlingEvents();// 處理事件
}

/**
 * 真實類
 */
public class RealClass implements ProxyInterface {

	@Override
	public void handlingEvents() {
	    System.out.println("正在處理事件中......");
	}
}

/**
 * 代理類
 */
public class ProxyClass implements ProxyInterface {
	private ProxyInterface real;
	
	public Pursuit(ProxyInterface real) {
	    this.real = real;
	}
 
	@Override
	public void handlingEvents() {
	    // todo 執行真實類方法以前能夠在此作處理
	    real.handlingEvents();
	    // todo 執行真實類方法以後能夠在此作處理
	}
}

/**
 * 調用
 */
public static void main(String[] args) {
    RealClass real = new RealClass();
    ProxyClass daili = new ProxyClass(real);
    daili.handlingEvents();
}
複製代碼

代碼說明:bash

上述代碼示例中,代理類調用真實類的同名方法,此時能夠對真實類中的方法執行先後進行處理,好比:計算真實類中方法執行時間或者在真實類方法執行先後分別打印Log等等...,而在AIDL生成的Stub類中的代理類中,就在跨進程間方法執行先後分別調用了寫入和讀取驅動操做,此話題後續再詳細分析。代理類的好處:代理類實現和真實類同樣的接口,所以不一樣代理類實現同一接口的代理類之間,也能夠相互代理,下面用代碼說明一下:服務器

/**
 * 代理類1
 */
public class ProxyClass1 implements ProxyInterface {
	private ProxyInterface real;
	
	public Pursuit(ProxyInterface real) {
	    this.real = real;
	}
 
	@Override
	public void handlingEvents() {
	    System.out.println("方法前");
	    real.handlingEvents();
	    System.out.println("方法後");
	}
}

/**
 * 代理類2
 */
public class ProxyClass2 implements ProxyInterface {
	private ProxyInterface real;
	
	public Pursuit(ProxyInterface real) {
	    this.real = real;
	}
 
	@Override
	public void handlingEvents() {
	    long startTime=System.currentTimeMillis();
	    real.handlingEvents();
	    long endTime=System.currentTimeMillis();
	    float excTime=(float)(endTime-startTime)/1000;
	    System.out.println("執行時間:"+excTime+"s");
	}
}

/**
 * 調用
 */
public static void main(String[] args) {
    RealClass real = new RealClass();
    ProxyClass1 daili1 = new ProxyClass1(real);
    ProxyClass2 daili2 = new ProxyClass2(daili1);
    daili2.handlingEvents();
}
複製代碼

上述示例代碼中,只是用計時以及打印來表示代理模式的用法以及部分場景,固然場景不止這些,先放出一部分AIDL動態生成的Stub類的代理類中的部分代碼:架構

/**
  首先就是建立了3個對象_data 輸入對象,_reply輸出對象,_result返回值對象而後把參數信息 寫入到_data裏,
  接着就調用了transact這個方法 來發送rpc請求,而後接着當前線程掛起, 服務端的onTransace方法才被調用,
  調用結束之後 當前線程繼續執行,直到從_reply中取出rpc的返回結果 而後返回_reply的數據
  注:有返回值的方法纔有_result對象
*/
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.administrator.aidlmessagetest.Person> _result;
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();
  _result = _reply.createTypedArrayList(com.example.administrator.aidlmessagetest.Person.CREATOR);
} finally {
  _reply.recycle();
  _data.recycle();
}
return _result;
複製代碼

動態代理:框架

如今要生成某一個對象的代理對象,這個代理對象一般也要編寫一個類來生成,因此首先要編寫用於生成代理對象的類。在java中如何用程序去生成一個對象的代理對象呢,java在JDK1.5以後提供了一個"java.lang.reflect.Proxy"類,經過"Proxy"類提供的一個newProxyInstance方法用來建立一個對象的代理對象。

Proxy類中的方法:(最經常使用的方法就是:newProxyInstance)

  • 方法 1: 該方法用於獲取指定代理對象所關聯的InvocationHandler

    static InvocationHandler getInvocationHandler(Object proxy) 
    複製代碼
  • 方法 2:該方法用於獲取關聯於指定類裝載器和一組接口的動態代理類的類對象

    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
    複製代碼
  • 方法 3:該方法用於判斷指定類是不是一個動態代理類

    static boolean isProxyClass(Class cl) 
    複製代碼
  • 方法 4:該方法用於爲指定類裝載器、一組接口及調用處理器生成動態代理類實例

    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
    複製代碼

JDK中的動態代理是經過反射類Proxy以及InvocationHandler回調接口實現的;可是,JDK中所要進行動態代理的類必需要實現一個接口,也就是說只能對該類所實現接口中定義的方法進行代理,這在實際編程中具備必定的侷限性,並且使用反射的效率也並非很高。

JDK動態代理的方案:

  • JDK自帶的(Proxy)
  • ASM (Bytecode Proxy): ASM在建立class字節碼的過程當中,操縱的級別是底層JVM的彙編指令級別,這要求ASM使用者要對class組織結構和JVM彙編指令有必定的瞭解(須要手動生成)。
  • CGLIB (基於ASM包裝)
  • JAVAASSIST (Proxy / BytecodeProxy): Javassist是一個開源的分析、編輯和建立Java字節碼的類庫。 是由東京工業大學的數學和計算機科學系的 Shigeru Chiba (千葉 滋)所建立的。它已加入了開放源代碼JBoss 應用服務器項目,經過使用Javassist對字節碼操做爲JBoss實現動態AOP框架。javassist是jboss的一個子項目,其主要的優勢,在於簡單,並且快速。直接使用java編碼的形式,而不須要了解虛擬機指令,就能動態改變類的結構,或者動態生成類。

Bytecode Proxy:直接生成二進制字節碼格式的代理類

動態代理方案性能對比:

  1. ASM和JAVAASSIST Bytecode Proxy生成方式不相上下,都很快,是CGLIB的5倍。
  2. CGLIB次之,是JDK自帶的兩倍。
  3. JDK自帶的再次之,因JDK1.6對動態代理作了優化,若是用低版本JDK更慢,要注意的是JDK也是經過字節碼生成來實現動態代理的,而不是反射。
  4. JAVAASSIST Proxy動態代理接口最慢,比JDK自帶的還慢。

(這也是爲何網上有人說JAVAASSIST比JDK還慢的緣由,用JAVAASSIST最好別用它提供的動態代理接口,而能夠考慮用它的字節碼生成方式)

差別緣由: 各方案生成的字節碼不同,像JDK和CGLIB都考慮了不少因素,以及繼承或包裝了本身的一些類, 因此生成的字節碼很是大,而咱們不少時候用不上這些,而手工生成的字節碼很是小,因此速度快。

哈哈哈,盜張圖片清醒一下

接下來從新進入JDK提供的動態代理方式:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler handler),這個方法有3個參數,下面分別來看一看:

  • loader:用哪一個類加載器去加載代理對象
  • interfaces:動態代理類須要實現的接口
  • handler:動態代理方法在執行時,會調用handler裏面的invoke方法去執行,而且若須要在真實類方法執行先後作相應處理,也是在InvocationHandler裏,以下面代碼所示:
public class TestInvacationHandler implements InvocationHandler {
    // 被代理的真實對象
    private final ProxyInterface object;
    public TestInvacationHandler(ProxyInterface object){
        this.object = object;
    }
    /**
     * proxy:就是代理對象,newProxyInstance方法的返回對象
     *  proxy的做用:
     *      1. 可使用反射獲取代理對象的信息(也就是proxy.getClass().getName())。
     *      2. 能夠將代理對象返回以進行連續調用,這就是proxy存在的目的。由於this並非代理對象,
     * method:調用的方法
     * args: 方法中的參數
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 方法執行前
        System.out.println("---------before-------");
        // 調用方法
        Object invoke = method.invoke(object, args);
//        Object invoke = method.invoke(proxy, args); // 若此處使用此句代碼,則該程序會造成死循環
        // 方法執行後
        System.out.println("---------after-------");
        return invoke;
    }
}
複製代碼

結合上面的ProxyInterface RealClass以及TestInvacationHandler完整調用:

public static void main(String[] args) {
    ProxyInterface real = new RealClass();
    ProxyInterface proInterface = (ProxyInterface)Proxy
        .newProxyInstance(real.getClass().getClassLoader()
        ,RealClass.class.getInterfaces(), new TestInvacationHandler(real));
    proInterface.handlingEvents();
}
複製代碼

說到這裏,其實就會引發一個頗有意思的問題,咱們爲何要使用動態代理?

答:從動態代理的完整調用能夠看出,動態代理使咱們免於去重寫接口中的方法,
而着重於去擴展相應的功能或是方法的加強,與靜態代理相比簡單了很多,能夠減小項目中的業務量
複製代碼

至此,禮敬,代理模式到此已結束,如有什麼地方闡述有誤,敬請指正。

Binder機制

繼續盜圖,哈哈哈,接下來終於到了Binder機制啦!!!先來張圖清爽一下

此圖爲Service跨進程間通信綁定服務時序圖,Service綁定流程中也牽扯着IBinder調用,好比IActivityManager(ActivityManagerService)。

Binder機制基本瞭解:

  • 進程通訊
    1. 進程隔離:出於安全考慮,一個進程不能操做另外一個進程的數據,進而一個操做系統必須具有進程隔離這個特性。在Linux系統中,虛擬內存機制爲每一個進程分配了線性連續的內存空間,操做系統將這種虛擬內存空間映射到物理內存空間,每一個進程有本身的虛擬內存空間,進而不能操做其餘進程的內存空間,每一個進程只能操做本身的虛擬內存空間,只有操做系統纔有權限操做物理內存空間。進程隔離保證了每一個進程的內存安全,可是在大多數情形下,不一樣進程間的數據通信是不可避免的,所以操做系統必須提供跨進程通訊機制。 進程空間分爲內核空間和用戶空間,內核空間(Kernel)是系統內核運行的空間。用戶空間(User Space)是用戶程序運行的空間,他們之間是隔離的。內核有訪問的全部權限,用戶空間也能夠經過系統接口去訪問內核空間。用戶空間能夠經過內核空間(相似於中介者)進行相互訪問。

    2. Binder機制優勢:

      傳輸性能好:

      • Socket:是一個通用接口,致使其傳輸效率低,開銷大
      • 共享內存:雖然在傳輸時不須要拷貝數據,但其控制機制複雜
      • Binder:複雜數據類型傳遞能夠複用內存,須要拷貝1次數據
      • 管道和消息隊列:採用存儲轉發方式,至少須要拷貝2次數據,效率低

      穩定性:

      • Binder基於C/S架構,Server端和Client端相對獨立,穩定性好。
      • 共享內存沒有Server端和Client端的區分,可能存在同步死鎖等問題。Binder穩定性優於共享內存。

      安全性高:

      • 傳統的進程:通訊方式對於通訊雙方的身份並無作出嚴格的驗證,只有在上層協議上進行架設
      • Binder機制:從協議自己就支持對通訊雙方作身份校檢,於是大大提高了安全性
    3. Binder機制實現原理圖:(借圖,下面會註明來處)

Binder機制之AIDL:

接下來根據生成的aidl文件源碼分析跨進程間通信(aidl生成:new->AIDL->AIDL File-> Build) 原諒我又把別人寫好的借來(下面會註明來處)

/**
    標準的代理模式,代理類爲Proxy,此文件爲動態生成,所以Stub Proxy都爲固定類名
*/
package com.example.administrator.aidlmessagetest;

//從前面幾行就能看出來 生成的代碼是一個 interface ,只不過這個interface是 android.os.IInterface 的子類!
public interface IPersonManager extends android.os.IInterface {

    //這個接口裏 有一個靜態的抽象類Stub
    //這個Stub是Binder的子類,而且實現了IPersonManager 這個接口
    public static abstract class Stub extends android.os.Binder implements com.example.administrator.aidlmessagetest.IPersonManager {
    
        //這個東西就是惟一的binder標示 能夠看到就是IPersonManager的全路徑名
        private static final java.lang.String DESCRIPTOR = "com.example.administrator.aidlmessagetest.IPersonManager";

        /**
         * 這個就是Stub的構造方法,咱們若是寫好aidl文件之後 寫的service裏面 是怎麼寫的?
         * private final IPersonManager.Stub mBinder = new IPersonManager.Stub() {}
         * 咱們都是這麼寫的 對吧~~因此想一想咱們的service裏面的代碼 就能輔助理解 這裏的代碼了
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        //這個方法 其實就作了一件事,若是是同一個進程,那麼就返回Stub對象自己
        //若是不是同一個進程,就返回Stub.Proxy這個代理對象了
        public static com.example.administrator.aidlmessagetest.IPersonManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            //若是是同1個進程,也就是說進程內通訊的話 咱們就返回括號內裏的對象
            if (((iin != null) && (iin instanceof com.example.administrator.aidlmessagetest.IPersonManager))) {
                return ((com.example.administrator.aidlmessagetest.IPersonManager) iin);
            }
            //若是不是同一進程,是2個進程之間相互通訊,那咱們就得返回這個Stub.Proxy 看上去叫Stub 代理的對象了
            return new com.example.administrator.aidlmessagetest.IPersonManager.Stub.Proxy(obj);
        }

        //返回當前對象
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        
        /**
         * 只有在跨進程通訊的時候 纔會調用這個方法 ,同一個進程是不會調用的。
         *
         * 首先 咱們要明白 這個方法 通常狀況下都是返回true的,
         * 也只有返回true的時候纔有意義,若是返回false了就表明這個方法執行失敗
         * 因此咱們一般是用這個方法來作權限認證的,其實也很好理解,既然是多進程通訊,
         * 那麼咱們服務端的進程固然不但願誰都能過來調用因此權限認證是必須的,權限認證先略過
         * 
         * 除此以外 ,onTransact 這個方法就是運行在Binder線程池中的,
         * 通常就是客戶端發起請求,而後android底層代碼把這個客戶端發起的請求封裝成3個參數,
         * 來調用這個onTransact方法,第一個參數code就表明客戶端想要調用服務端方法的標誌位。
         * 其實也很好理解 服務端可能有n個方法 每一個方法都有一個對應的int值來表明,
         * 這個code就是這個int值,用來標示客戶端想調用的服務端的方法
         * data就是方法參數,reply就是方法返回值。都很好理解
         *
         * 其實隱藏了很重要的一點,這個方法既然是運行在binder線程池中的,
         * 因此在這個方法裏面調用的服務器方法也是運行在Binder線程池中的,
         * 因此咱們要記得 若是你的服務端程序有可能和多個客戶端相聯的話,
         * 你方法裏使用的那些參數 必需要是支持異步的,不然的話值就會錯亂了!
         * 這點必定要記住!結論就是Binder方法 必定要是同步方法!!!!!!
        */
        @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_getPersonList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.example.administrator.aidlmessagetest.Person> _result = this.getPersonList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addPerson: {
                    data.enforceInterface(DESCRIPTOR);
                    com.example.administrator.aidlmessagetest.Person _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.example.administrator.aidlmessagetest.Person.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addPerson(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        // 只有在跨進程通訊的狀況下  纔會返回這個代理的對象
        private static class Proxy implements com.example.administrator.aidlmessagetest.IPersonManager {
            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;
            }

            /**
             * 這裏有2個方法 一個getPersonList 一個addPerson,咱們就分析一個方法就能夠了
             *
             * 首先就是建立了3個對象,
             * _data 輸入對象,_reply輸出對象,_result返回值對象,把參數信息寫入到_data裏,
             * 接着就調用了transact這個方法來發送rpc請求,而後接着當前線程掛起,
             * 服務端的onTransace方法才被調用,調用結束*之後當前線程繼續執行,
             * 直到從_reply中取出rpc的返回結果真後返回_reply的數據因此這裏咱們就要注意了,
             * 客戶端發起調用遠程請求時,當前客*戶端的線程就會被掛起了,
             * 因此若是一個遠程方法 很耗時,
             * 咱們客戶端就必定不能在ui main線程裏在發起這個rpc請求,否則就anr了。
             *
             * 注:這2個方法運行在客戶端!!!!!!!!!!!!!!!!
             * 注:若方法無返回值,則無_result返回值對象
            */
            @Override
            public java.util.List<com.example.administrator.aidlmessagetest.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.example.administrator.aidlmessagetest.Person> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.example.administrator.aidlmessagetest.Person.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addPerson(com.example.administrator.aidlmessagetest.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();
                }
            }
        }

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

    public java.util.List<com.example.administrator.aidlmessagetest.Person> getPersonList() throws android.os.RemoteException;

    public void addPerson(com.example.administrator.aidlmessagetest.Person person) throws android.os.RemoteException;
}
複製代碼

bindService時序圖中綁定服務操做是使用IBinder進行的,那麼IBinder是如何獲取的,何時初始化好的?從上述代碼分析來看,只能從Stub()的構造方法入手,Stub構造方法一調用其父類構造方法順便也會被調用,其父類構造方法代碼以下:

public Binder() {
    // private static native long getNativeBBinderHolder();
    // getNativeBBinderHolder 爲native方法
    mObject = getNativeBBinderHolder();
    NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);

    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Binder> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
            (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Binder class should be static or leaks might occur: "     +klass.getCanonicalName());
        }
    }
}
複製代碼

從Binder構造方法能夠看出,只要Stub被初始化就會調用Binder構造方法,從而會調用native層c/c++代碼初始化一個IBinder,並從內核Binder驅動中進行註冊。

繼續看上述aidl生成的代碼,要明確的是Proxy代理類是在客戶端(即調用端),因此裏面的方法也都是客戶端調用,而onTransact方法則是在服務端(即目標端,跨進程訪問端)。其方法調用過程就是:Proxy類方法(即客戶端)調用mRemote.transact(),會觸發目標端執行Binder::onTransact()(即上述代碼中的onTransact方法),咱們能夠看看transact()方法和onTransact()方法的參數對比:

/*
    參數一:用來標識指令,即調用的什麼方法。須要客戶端和服務端約定好code碼。
    參數二:來自發送端的數據包。包含參數信息
    參數三:來自發送端的接收包,往這個包中寫數據,就至關於給發送端返回數據。
    參數四:特殊操做標識。
*/
public boolean transact(int code, Parcel data, Parcel reply, int flags){}
public boolean onTransact(int code, Parcel data, Parcel reply, int flags){}
複製代碼

能夠看出來,兩個是成對的操做。mRemote.transact()操做是一個阻塞式的操做,就是說在這個方法執行返回成功後,直接從reply中讀取的數據就是遠程端在Binder::onTransact()中填充的數據。而編譯器自動幫咱們生成的onTransact()中,會讀取data中數據,而後調用對應的方法。大體調用狀況以下圖:

至此,完畢。

最後盜圖,哈哈

本文參考博客:

想研究更底層Binder機制可參考:www.jianshu.com/p/fe816777f…

敬請指教

相關文章
相關標籤/搜索