AIDL

作者: passerbywhu | 来源:发表于2017-12-27 10:46 被阅读16次

    虽然跨进程通信的内在机制十分复杂。但是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层中完全对应的继承关系。这里简单了解一下即可。

    相关文章

      网友评论

          本文标题:AIDL

          本文链接:https://www.haomeiwen.com/subject/oabygxtx.html