Android Binder通信原理--10:AIDL原理分析-Proxy-Stub设计模式

本文转载自:Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式

本文基于Android 10.0源码分析

1.摘要

  本节主要来讲解Android10.0 AIDL的通信原理。

2.概述

  上一节我们写了一个AIDL的示例,实现了两个应用之间的通信,这一节我们就来一起探讨下AIDL是如何生效的。

3.什么是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)服务。

4.为什么要用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交互的逻辑通过工具编译来生成。

5.AIDL通信流程

(1)Client端和Server端使用同一个AIDL,连包名都需要保持一致。

(2)Server端继承自Service,重载一个onBind(),返回服务实体Stub(),Stub提供了一个asInterface(Binder)的方法,如果是在同一个进程下那么asInterface()将返回Stub对象本身,否则返回Stub.Proxy对象。

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

(3)Client绑定服务时通过拿到服务Stub.asInterface返回的服务的代理Stub.Proxy()。

myService = IMyService.Stub.asInterface(service); // 不同进程获取到Stub.Proxy()

(4)Client和Server交互的简单示意流程。

Binder10-1.PNG

从上面的示例来看,服务本地拿到了AIDL生成的服务实体Stub(), Client绑定服务后,拿到了服务的代理Stub.proxy()。这和我们在前面Framewok层讲解的比较类似了,Client拿到BinderProxy对象,Server拿到Binder实体对象。

  AIDL在这里用到了一个Proxy-Stub (代理-存根)的设计模式,下面我们就这种设计模式来展开说明一下。

  Binder通信的数据流转如下图所示:

Binder10-2.PNG

6.proxy-stub设计模式

  Proxy将特殊性接口转换成通用性接口,Stub将通用性接口转换成特殊性接口,二者之间的数据转换通过Parcel(打包)进行的,Proxy常作为数据发送代理,通过Parcel将数据打包发送,Stub常作为数据接收桩,解包并解析Parcel Data package。

Binder10-3.PNG

(1)举例理解Proxy-Stub
  假如我们现在要看电视,我是客户Client,遥控器是代理Proxy,电视机是实体(播放画面,展示功能),遥控器传给电视机的蓝牙、红外参数为Parcel数据。
  我按下了遥控器的一些按键:提升音量,遥控器之前跟电视机做了绑定,可以拿到电视机的对象--实体Stub,把按键的操作组装成一个Parcel数据,发给电视机-Server,电视机-Server拿到请求后,执行相应的处理-提升音量,结果返回给遥控器,我们操作完成(这一步其实没有,我们只是假想一下)。这样就完成了Proxy-Stub的数据交互流程。

(2)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()代理
}

7.AIDL原理分析

  在上一节,IMyService.aidl编译后,Android Studio自动生成了IMyService.java文件,我们来看看这个文件的内容:

// 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); // 同进程返回Stub实体
      }
      return new com.android.myservice.IMyService.Stub.Proxy(obj); // 不同进程返回Proxy
    }

    @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;
}

(1)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。

(2)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。

Binder10-4.png

8.AIDL的配置方法

  AIDL使用一种简单语法,允许您通过一个或多个方法(可接收参数和返回值)来声明接口。参数和返回值可为任意类型,甚至是AIDL生成的其他接口。
  您必须使用Java 编程语言构建 .aidl 文件。每个.aidl 文件均须定义单个接口,并且只需要接口声明和方法签名。

(1)默认情况下,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。

(2)定义服务接口时,请注意:

  • 方法可带零个或多个参数,返回值或空值;

  • 所有非原语参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout(见下方示例);

  • 原语默认为 in,不能是其他方向。

oneway关键字用于修改远程调用的行为。使用此关键字后,远程调用不会屏蔽,而只是发送事务数据并立即返回。最终接收该数据时,接口的实现会将其视为来自Binder线程池的常规调用(普通的远程调用)。如果oneway用于本地调用,则不会有任何影响,且调用仍为同步调用。

9.总结

  Client通过Proxy向Server进行请求,最终进入Binder Driver,binder根据不同的事务处理,发送给Binder实体,实体中根据不同的TRANSACTION code转入不同的逻辑处理,处理完得到结果后,会把数据组装为Parcel,通过reply发送出来,Client收到reply的数据,进行最终流程处理。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容