Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub設計模式

摘要:本節主要來講解Android10.0 AIDL的通信原理

閱讀本文大約需要花費24分鐘。

文章首發微信公衆號:IngresGe

專注於Android系統級源碼分析,Android的平臺設計,歡迎關注我,謝謝!

[Android取經之路] 的源碼都基於Android-Q(10.0) 進行分析

[Android取經之路] 系列文章:

《系統啓動篇》

  1. Android系統架構
  2. Android是怎麼啓動的
  3. Android 10.0系統啓動之init進程
  4. Android10.0系統啓動之Zygote進程
  5. Android 10.0 系統啓動之SystemServer進程
  6. Android 10.0 系統服務之ActivityMnagerService
  7. Android10.0系統啓動之Launcher(桌面)啓動流程
  8. Android10.0應用進程創建過程以及Zygote的fork流程
  9. Android 10.0 PackageManagerService(一)工作原理及啓動流程
  10. Android 10.0 PackageManagerService(二)權限掃描
  11. Android 10.0 PackageManagerService(三)APK掃描
  12. Android 10.0 PackageManagerService(四)APK安裝流程

《日誌系統篇》

  1. Android10.0 日誌系統分析(一)-logd、logcat 指令說明、分類和屬性
  2. Android10.0 日誌系統分析(二)-logd、logcat架構分析及日誌系統初始化
  3. Android10.0 日誌系統分析(三)-logd、logcat讀寫日誌源碼分析
  4. Android10.0 日誌系統分析(四)-selinux、kernel日誌在logd中的實現​

《Binder通信原理》

  1. Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
  2. Android10.0 Binder通信原理(二)-Binder入門篇
  3. Android10.0 Binder通信原理(三)-ServiceManager篇
  4. Android10.0 Binder通信原理(四)-Native-C\C++實例分析
  5. Android10.0 Binder通信原理(五)-Binder驅動分析
  6. Android10.0 Binder通信原理(六)-Binder數據如何完成定向打擊
  7. Android10.0 Binder通信原理(七)-Framework binder示例
  8. Android10.0 Binder通信原理(八)-Framework層分析
  9. Android10.0 Binder通信原理(九)-AIDL Binder示例
  10. Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub設計模式
  11. Android10.0 Binder通信原理(十一)-Binder總結

1.概述

上一節我們寫了一個AIDL的示例,實現了兩個應用之間的通信,這一節我們就來一起探討下AIDL是如何生效的。

 

2.什麼是AIDL

AIDL:Android Interface Definition Language,即Android接口定義語言。

Android系統中的進程之間不能共享內存,因此,需要提供一些機制在不同進程之間進行數據通信。爲了使其他的應用程序也可以訪問本應用程序提供的服務,Android系統採用了遠程過程調用(Remote Procedure Call,RPC)方式來實現。與很多其他的基於RPC的解決方案一樣,Android使用一種接口定義語言(Interface Definition Language,IDL)來公開服務的接口。我們知道4個Android應用程序組件中的3個(Activity、BroadcastReceiver和ContentProvider)都可以進行跨進程訪問,另外一個Android應用程序組件Service同樣可以。因此,可以將這種可以跨進程訪問的服務稱爲AIDL(Android Interface Definition Language)服務。

 

3.爲什麼要用AIDL

Android中每個應用都是獨立的進程,擁有自己的虛擬機,虛擬地址,應用之間的內存不止不能互相訪問,存在應用隔離,因此兩個應用不能像面嚮對象語言一樣直接進行接口的調用。兩個進程之間的調用叫做IPC(進程間通信)。在Binder的起始章節,我們瞭解到Android中進程之間的IPC調用有:管道、共享內存、消息隊列、信號量、socket、binder,在《Binder入門篇》中,從性能、安全角度分別講解了各個IPC通信的優缺點,最終我們選擇了Binder。

那麼既然我們有了Binder,爲什麼還要有AIDL呢?

在我們前面的 《Framrwork binder示例》 中,我們知道,通過binder來進行client\server時,我們寫了完成的服務創建和client獲取流程,在上一節AIDL示例中,我們寫完AIDL 編譯後,發現生成的IMyService.java文件就和我們在Framework中寫的類似,AIDL簡化了Binder的代碼邏輯,把跟Service交互的邏輯通過工具編譯來生成。

 

4.AIDL通信流程

Client 端和Server端使用同一個AIDL,連包名都需要保持一致。

Server端繼承自Service,重載一個onBind(),返回服務實體Stub(),Stub提供了一個asInterface(Binder)的方法,如果是在同一個進程下那麼asInterface()將返回Stub對象本身,否則返回Stub.Proxy對象.

Code:

IMyService.Stub mStub = new IMyService.Stub(){...};
@Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind");
        return mStub;//通過ServiceConnection在activity中拿到MyService
}

Client 綁定服務時通過拿到服務Stub.asInterface返回的服務的代理Stub.Proxy()

Code:

myService = IMyService.Stub.asInterface(service);

Client和Server交互的簡單示意流程:

從上面的示例來看,服務本地拿到了AIDL生成的服務實體Stub(), Client綁定服務後,拿到了服務的代理Stub.proxy()。這和我們在前面Framewok層講解的比較類似了,Client拿到BinderProxy對象,Server拿到Binder實體對象。

AIDL在這裏用到了一個Proxy-Stub  (代理-存根)的設計模式,下面我們就這種設計模式來展開說明一下。

Binder通信的數據流轉如下圖所示:

5.proxy-stub 設計模式

Proxy將特殊性接口轉換成通用性接口,Stub將通用性接口轉換成特殊性接口,二者之間的數據轉換通過Parcel(打包)進行的,Proxy常作爲數據發送代理,通過Parcel將數據打包發送,Stub常作爲數據接收樁,解包並解析Parcel Data package。

舉例理解Proxy-Stub:

假如我們現在要看電視,我是客戶Client,遙控器是代理Proxy,電視機是實體(播放畫面,展示功能),遙控器傳給電視機的藍牙、紅外參數爲Parcel數據。

我按下了遙控器的一些按鍵-提升音量,遙控器之前跟電視機做了綁定,可以拿到電視機的對象--實體Stub,把按鍵的操作組裝成一個Parcel數據,發給電視機-Server,電視機-Server拿到請求後,執行相應的處理-提升音量,結果返回給遙控器,我們操作完成(這一步其實沒有,我們只是假想一下)。這樣就完成了Proxy-Stub的數據交互流程。

 

Proxy和Stub的說明:

  1. Stub 跟 Proxy 是一對,俗稱“代理-存根”,一般用在遠程方法調用
  2. Proxy 的接口供客戶端程序調用,然後它內部會把信息包裝好,以某種方式傳遞給 Stub,而後者通過對應的接口作用於服務端系統,從而完成了“遠程調用”
  3. AIDL中,Stub爲服務實體;Stub.Proxy()爲服務的代理,都是通過Stub.asInterface(IBinder)中獲取,可以通過AIDL生成的java文件看出。
public static com.android.myservice.IMyService asInterface(android.os.IBinder obj)
{
    if ((obj==null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof com.android.myservice.IMyService))) {
        return ((com.android.myservice.IMyService)iin); //如果是同一進程,返回的是服務Stub本身
    }
    return new com.android.myservice.IMyService.Stub.Proxy(obj); //如果是不同進程,則返回Stub.Proxy()代理
}

6.AIDL原理分析

在上一節,IMyService.aidl編譯後,Android Studio自動生成了IMyService.java文件,我們來看看這個文件的內容:

Code:IMyService.java


/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.android.myservice;
// Declare any non-default types here with import statements

public interface IMyService extends android.os.IInterface
{
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.android.myservice.IMyService
{
    private static final java.lang.String DESCRIPTOR = "com.android.myservice.IMyService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
{
      this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an com.android.myservice.IMyService interface,
     * generating a proxy if needed.
     */
    public static com.android.myservice.IMyService asInterface(android.os.IBinder obj)
{
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.android.myservice.IMyService))) {
        return ((com.android.myservice.IMyService)iin);
      }
      return new com.android.myservice.IMyService.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
{
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_basicTypes:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0!=data.readInt());
          float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_add:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          int _arg1;
          _arg1 = data.readInt();
          int _result = this.add(_arg0, _arg1);
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }

    private static class Proxy implements com.android.myservice.IMyService
{
      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;
      }

      /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
      @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(anInt);
          _data.writeLong(aLong);
          _data.writeInt(((aBoolean)?(1):(0)));
          _data.writeFloat(aFloat);
          _data.writeDouble(aDouble);
          _data.writeString(aString);
          mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public int add(int num1, int num2) throws android.os.RemoteException
{
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(num1);
          _data.writeInt(num2);
          mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
          _reply.readException();
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
    }
    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
  }
  /**
   * Demonstrates some basic types that you can use as parameters
   * and return values in AIDL.
   */
  public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
  public int add(int num1, int num2) throws android.os.RemoteException;
}

IMyService.java 的說明:

  1. 有一個Stub的抽象類,Stub中又有一個Proxy的抽象類
  2. Stub.asInterface(IBinder) 會根據是同一進行通信,還是不同進程通信,返回Stub()實體,或者Stub.Proxy()代理對象
  3. Stub()的Binder實體中有個onTransact()函數,在前面的一些Binder Native、Framework的流程,我們知道,服務最終處理的入口就是onTransact(),這裏會解析Client傳來的 TRANSACTION code ,解析Parcel數據,調用對應的服務接口處理
  4. Proxy()中存在一個asBinder(),返回的對象爲mRemote,就如我們前面Framework瞭解的,對應的其實是BinderProxy對象。
  5. Proxy()組裝了Client中的AIDL接口的核心實現,組裝Parcel數據,調用BinderProxy()的transact()發送TRANSACTION code

 

 

AIDL的具體流程如下:

  1. Client和Server都使用同一個AIDL文件,包名相同,編譯後,兩邊都會生成IMyService.java,其中有Stub實體和Proxy代理兩個對象
  2. Server端通過AndroidManifest.xml 註冊Service
  3. Client通過bindService()獲得服務的代理Stub.Proxy()
  4. Client 調用AIDL的方法add(),其實調用的是IMyService.java中的Stub.Proxy.add(),最終通過BinderProxy.java的transact()向服務端發送
  5. 通過Binder驅動的流程,進入到服務端的onTransact(),根據Client發送的TRANSACTION code,解析進入相應的流程處理,進入add()
  6. MyService在被綁定時,有了實體IMyService.Stub,最終進入MyService.java的add()處理,完成接口調用,調用完成後把數據寫入Parcel,通過reply發送給Client

7.AIDL的配置方法

AIDL 使用一種簡單語法,允許您通過一個或多個方法(可接收參數和返回值)來聲明接口。參數和返回值可爲任意類型,甚至是 AIDL 生成的其他接口。

您必須使用 Java 編程語言構建 .aidl 文件。每個 .aidl 文件均須定義單個接口,並且只需要接口聲明和方法簽名。

默認情況下,AIDL 支持下列數據類型:

  • Java 編程語言中的所有原語類型(如 int、long、char、boolean 等)

  • String

  • CharSequence

  • List

List 中的所有元素必須是以上列表中支持的數據類型,或者您所聲明的由 AIDL 生成的其他接口或 Parcelable 類型。您可選擇將 List 用作“泛型”類(例如,List<String>)。儘管生成的方法旨在使用 List 接口,但另一方實際接收的具體類始終是 ArrayList。

  • Map

Map 中的所有元素必須是以上列表中支持的數據類型,或者您所聲明的由 AIDL 生成的其他接口或 Parcelable 類型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。儘管生成的方法旨在使用 Map 接口,但另一方實際接收的具體類始終是 HashMap。

 

定義服務接口時,請注意:

方法可帶零個或多個參數,返回值或空值。

所有非原語參數均需要指示數據走向的方向標記。這類標記可以是 in、out 或 inout(見下方示例)。

原語默認爲 in,不能是其他方向。

 

oneway 關鍵字用於修改遠程調用的行爲。使用此關鍵字後,遠程調用不會屏蔽,而只是發送事務數據並立即返回。最終接收該數據時,接口的實現會將其視爲來自 Binder 線程池的常規調用(普通的遠程調用)。如果 oneway 用於本地調用,則不會有任何影響,且調用仍爲同步調用。

 

8.總結

Client 通過Proxy向Server進行請求,最終進入Binder Driver,binder根據不同的事務處理,發送給Binder實體,實體中根據不同的TRANSACTION code轉入不同的邏輯處理,處理完得到結果後,會把數據組裝爲Parcel,通過reply發送出來,Client收到reply的數據,進行最終流程處理。

 

參考:

《Android中AIDL的工作原理》

《從一個例子開始分析AIDL原理》 

《Android Binder 完全解析(三)AIDL實現原理分析》

《Android 接口定義語言 (AIDL)》

《Android中AIDL的使用詳解》

《從IBinder接口學習Proxy-Stub設計模式》

《Android進程間通信-AIDL實現原理》

 

我的微信公衆號:IngresGe

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章