AIDL

虽然跨进程通信的内在机制十分复杂。但是Android为我们提供了一套非常简单的机制,让我们仅需要很少的步骤就能完成一次跨进程服务的调用。这个机制就是AIDL。

在开始介绍AIDL之前,按惯例先举一个生活中的例子方便理解。

举个栗子

A和B相隔两地,路途遥远。只能靠电话进行通讯。B由于在科研机构工作,有一台电脑,上面有很多计算的程序。A和B经过电话沟通知道了B电脑上有哪些计算程序可以利用。比如add, subtract, multiply和divide.

这个时候A有一些复杂计算问题需要依靠计算机的程序计算能力进行解答。那么A应该如何做呢?

显然,A自己没有计算机,不能直接去操作计算机进行add, subtract, multiply和divide操作。

所以A打了个电话给B。告诉B,我需要调用你计算机上的add程序,参数是1和2。然后B在自己的电脑上运行了add程序,并且告知A,结果是3。

那么这个就是一次典型的RPC远程过程调用。

其中有几个关键。

第一:B的计算机上有哪些程序,每个程序的调用方式也即需要哪些参数,A是全部知晓的。

第二:A向B描述意图的方式。 需要用到的程序名以及按顺序告诉相应的参数。例子中是add 1 2

第三:B能够顺利解析A的意图,得知是需要运行add程序,参数为1和2,而后将运行结果告知A.

例子中还有两个隐含的关键内容:1、A有B的电话号码,通过电话号码可以找到B。2、电话可以通过语音进行内容传输

那么对应到Android AIDL中呢。

一、B的计算机上有哪些程序就是我们需要定义的AIDL接口所描述的内容。而AIDL工具生成的Proxy和Stub都会实现这个接口。Stub被放在服务端,Proxy被放在客户端。因此双端都是清楚有哪些方法可以被调用的,以及他们的调用方式,也即参数顺序。

二、A向B描述意图的方式,以及B解析A意图的方式。实际上是固定的一套模板,也即协议。在AIDL中,客户端将参数按顺序打包进一个Parcel对象中,然后以transaction_code指明需要调用的方法,传输给服务端。服务端通过transaction_code得知客户端需要调用的方法,从客户端传输过来的Parcel对象中解析出参数,进行方法调用。再将结果打包进Parcel对象中进行返回。其中Proxy和Stub的最主要的工作就是帮我们自动完成模式化的这部分工作。填充协议以及解析协议。

三、A是如何拿到B的联系方式,以及协议的内容是怎么在客户端和服务端之间进行传输的。这部分就是Binder驱动所要完成的工作。

我们主要探讨AIDL的实现细节。Binder驱动的内容会放到之后的文章里分析。

一个实例

我们以一个提供getVal()和setVal()的IValueService为例观察下AIDL的基本实现步骤。完整代码见https://github.com/passerbywhu/AIDL

1.  首先定义一个IValueService.AIDL文件。其内容如下:

interface IValueService {

    void setVal(int val);

    int getVal();

}

2. AndroidStudio解析这个文件之后会为我们生成IValueService.java文件。其中最主要的是生成了两个类

 IValueService.Stub类

Stub类继承自Binder并实现了IValueService接口。而Binder实现了IBinder接口。在服务端使用

 IValueService.Proxy类

Proxy也实现了IValueService接口,其构造函数依赖于一个IBinder类型的对象(实际类型是BinderProxy)在客户端使用


3. 服务端声明ValueService类继承自IValueService.Stub类,并实现setVal(int)和getVal()的逻辑

public class ValueService extends IValueService.Stub {

    public static int value;

 @Override

    public void setVal(int val) throws RemoteException {

        value = val;

    }

    @Override

    public int getVal() throws RemoteException {

         return value;

    }

}

4.  服务端声明一个Service组件继承自Service类,在其onBind方法中,返回ValueService类的实例。

public class ServiceContainer extends Service {   //这个Service我们会配置为在另一个进程启动作为远程服务端

    private IValueService.Stub mBinder = new ValueService();

    ...

    @Override

    public IBinder onBind(Intent intent) {

        return mBinder;

    }

   ...

}

5. 客户端在onServiceConnected(ComponentName name, IBinder service)中,以service为参数调用IValueService.Stub.asInterface(IBinder obj)来构造IValueService.Stub.Proxy对象。并借由Proxy对象来调用远程服务。

bindService(intent,  new ServiceConnection() {

...

@Override

//这里有个需要注意的点。我们在服务端的onBind中返回的是Binder类型。而客户端这边接收到的却是BinderProxy类型。这是Binder驱动在期间做了一些转换。

//不过这和我们当前的主题无关

public void onServiceConnected(ComponentName name, IBinder service) {

     //asInterface实际上调用的代码就是new IValueService.Stub.Proxy(service)。实际上就是new Proxy(service)

     IValueService.Stub.Proxy valueServiceProxy = (IValueService.Stub.Proxy) IValueService.Sub.asInterface(service);

     valueServiceProxy.setVal(3);

}

...

}, BIND_AUTO_CREATE);

以上就是在Android中利用AIDL机制实现远程过程调用的全部过程。

可以看到,在Android AIDL机制的封装下。服务端只需要关注自身的真正业务逻辑。即setVal和getVal的真正实现。

而客户端在获取Proxy对象之后,也可以像调用本地方法一样去调用服务端的方法。

那么按照我们之前的那个例子,协议的填充与解析的部分是在哪里完成的呢?显然,秘密只可能藏在Proxy以及Stub中。

IValueService.Stub.Proxy

private static class Proxy implements IValueService {

    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {   //相当于之前例子中B的电话号码

        mRemote = remote;

    }

    ...

    @Override

     public void setVal(int val) 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(val);  //按顺序写入方法调用的参数

                 //需要调用的方法对应的transaction_code以参数形式提供,因此没有放在_data中。

                //transact就相当于拨通电话,将语音内容传输给服务端

                 mRemote.transact(Stub.TRANSACTION_setVal, _data, _reply, 0);

                 _reply.readException(); //该方法没有返回值,因此仅需读取服务端返回的异常信息

           } finally {

               _reply.recycle();

               _data.recycle();

           }

     }

     @Override

     public int getVal() throws android.os.RemoteException {

          android.os.Parcel _data = android.os.Parcel.obtain();  //与setVal中一致

          android.os.Parcel _reply = android.os.Parcel.obtain(); //与setVal中一致

          int _result;

          try {

               _data.writeInterfaceToken(DESCRIPTOR);    //与setVal中一致

              mRemote.transact(Stub.TRANSACTION_getVal, _data, _reply, 0);  //与setVal中一致

              _reply.readException();  //与setVal中一致

              _result = _reply.readInt();  //解析返回的语音数据包中的内容。

          } finally {

              _reply.recycle();

             _data.recycle();

          }

          return _result;

     }

}

可见Proxy的主要功能就是按照事先商定好的协议规则填充好协议内容,并发送出去。为AIDL机制的使用者屏蔽掉了中间的细节。从而使客户端调用远程服务端的方法看起来和调用本地方法几无二致。

IValueService.Stub

public static abstract class Stub extends android.os.Binder implements IValueService {

     static final int TRANSACTION_setVal = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);  //需要调用的方法对应的code。客户端与服务端约定好的。

     static final int TRANSACTION_getVal = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

     ...

     @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_setVal: {

                  data.enforceInterface(DESCRIPTOR);  //身份认证

                  int _arg0;

                  _arg0 = data.readInt();  //按顺序读取参数

                  this.setVal(_arg0);  //调用真正的服务端实现方法

                  reply.writeNoException();

                  return true;

              }

            case TRANSACTION_getVal: {

                data.enforceInterface(DESCRIPTOR);

                int _result = this.getVal(); //因为是个无参方法,所以直接调用真正的实现方法。

               reply.writeNoException();  //写入异常信息

               reply.writeInt(_result);   //写入结果值

               return true;

            }

          return super.onTransact(code, data, reply. flags);

     }

     public int getVal();

     public void setVal(int val);

     ...

}

可见Stub的主要功能是解析客户端发过来的协议内容,确定需要调用的本地方法以及参数,并将结果返回。

整体流程图如下:


整个AIDL机制涉及到的类就这些。作为使用者,了解到这一步就已经能够熟练使用了。

但是纵观上面的整个流程,我们有两个问题。

1、我们在服务端onBind中返回的ValueService是怎么传到客户端的。ValueService明明是个Binder类型,为什么客户端在onServiceConnected中拿到的service却是一个BinderProxy类型呢?对应到最开始的例子,也就是A是怎么拿到B的电话号码的?

2、BinderProxy的transact方法究竟做了什么操作从而能把客户端的数据传到了服务端呢?对应到最开始的例子就是,语音信号是怎么进行编码的,而传输语音信号的电话线又是什么东西。

这两点也是Binder驱动的核心功能。我们会在Binder驱动相关的章节进行讨论。

最后

附上类关系图,方便更加直观的看清楚各个类在整个结构中的位置

Stub

Stub

Proxy


Proxy

由图中可知,Binder和BinderProxy都实现了IBinder接口,并且都持有一个mObject对象指向其JNI层对应的对象。 分别是BBinderBpBinder。 而真正完成进程间通信的就是这两个JNI层的对象。实际上,在JNI层上有和Java层中完全对应的继承关系。这里简单了解一下即可。



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

推荐阅读更多精彩内容