Binder機制之AIDL

若丶相見發表於2019-04-10

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

代理模式

代理模式的定義:代理模式屬於結構型模式,指的是為其他物件提供一種代理以控制對這個物件的訪問。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用。

應用場景:當Client為了實現Subject的目標而直接訪問RealSubject存在問題的時候(比如物件建立開銷很大,或者某些操作需要安全控制,或者需要程式外的訪問),就需要Proxy來代替Subject來執行request操作。例如:AOP切面程式設計 AIDL跨程式間通訊等

代理模式的主要角色如下:

  • 抽象(Subject)類:通過介面或抽象類宣告真實主題和代理物件實現的業務方法。
  • 真實(Real Subject)類:實現了抽象主題中的具體業務,是代理物件所代表的真實物件,是最終要引用的物件。
  • 代理(Proxy)類:提供了與真實主題相同的介面,其內部含有對真實主題的引用,它可以訪問、控制或擴充套件真實主題的功能。

類圖如下:

Binder機制之AIDL

程式碼示例:

/**
 * 代理介面
 */
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();
}
複製程式碼

程式碼說明:

上述程式碼示例中,代理類呼叫真實類的同名方法,此時可以對真實類中的方法執行前後進行處理,比如:計算真實類中方法執行時間或者在真實類方法執行前後分別列印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都考慮了很多因素,以及繼承或包裝了自己的一些類, 所以生成的位元組碼非常大,而我們很多時候用不上這些,而手工生成的位元組碼非常小,所以速度快。

Binder機制之AIDL

                                    哈哈哈,盜張圖片清醒一下
複製程式碼

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

  • loader:用哪個類載入器去載入代理物件
  • interfaces:動態代理類需要實現的介面
  • handler:動態代理方法在執行時,會呼叫handler裡面的invoke方法去執行,並且若需要在真實類方法執行前後做相應處理,也是在InvocationHandler裡,如下面程式碼所示:
public class TestInvacationHandler implements InvocationHandler {
    private final ProxyInterface proxy;
    public TestInvacationHandler(ProxyInterface proxy){
        this.proxy = proxy;
    }
    /**
        proxy:就是代理物件,newProxyInstance方法的返回物件
        method:呼叫的方法
        args: 方法中的引數
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 方法執行前
        System.out.println("---------before-------");
        // 呼叫方法
        Object invoke = method.invoke(proxy, args);
        // 方法執行後
        System.out.println("---------after-------");
    }
}
複製程式碼

結合上面的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機制之AIDL
繼續盜圖,哈哈哈,接下來終於到了Binder機制啦!!!先來張圖清爽一下

Binder機制之AIDL

此圖為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

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機制之AIDL

至此,完畢。

最後盜圖,哈哈

Binder機制之AIDL

本文參考部落格:

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

敬請指教

相關文章