Android Service aidl分析

Posted ITRenj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Service aidl分析相关的知识,希望对你有一定的参考价值。

Android Service 代码地址

在介绍正文之前,我们先看看几个知识点进程、线程和android中的进程间通讯(IPC)。

  • 线程:线程时CPU调度的最小单元,同时线程也是一种有限的系统资源。
  • 进程:进程一般表示一个执行单元,在PC和移动设备中一般可以表示一个程序或者应用(在一般情况下,一个应用程序是在同一进程中运行,但是也有可能一个应用有多个进程,比如:Android中可以在清单文件中为四大组件指定运行进程(process属性),而不是运行在默认进程。)。
  • Android中的进程间通讯方式
    • Bundle:通过Intent打开四大组件时,Bundle可以用来传递数据(四大组件之间传递,数据类型有限)
    • 文件共享:通过共享文件传递数据(不适合高并发,且进程之间的数据传递实时性不强)
    • AIDL方式:功能强大,支持并发和实时性,支持一对多传输(使用较为复杂)
    • Messenger:AIDL的一种简化形式(不支持并发,请求串行处理,且为一对一,数据类型有限,和Bundle一样)
    • ContentProvider:Android四大组件之一,强大的数据源处理能力,支持并发和一对多(主要用于对数据源的CURD操作)
    • Socket:套接字,功能强大,通过网络传输字节流数据,支持一对多并发实时通讯(实现较为复杂,主要用于网络数据交换)

在前面我们已经说过了使用AIDL进行IPC通讯,这篇文章主要说明一下我们使用aidl后缀时,系统帮我们做了哪些事情。我们根据以下 IRemoteBookBinder.aidl 文件来做分析:

interface IRemoteBookBinder 
    void addBookIn(in BookBean bookBean);

    void addBookOut(out BookBean bookBean);

    void addBookInOut(inout BookBean bookBean);

    List<BookBean> getBookList();

我们创建了 IRemoteBookBinder.aidl 文件,重新 build 后,Android studio会帮我们自动生成 IRemoteBookBinder.java 文件,Android Studio自动生成的java文件路径在 app\\build\\generated\\aidl_source_output_dir\\debug\\out\\包名 中。文件内容比较长,这里就不直接将代码完全粘贴出来了,在下面分步说明。下图为 IRemoteBookBinder.java 类组织图:

强烈建议:在看具体分析时,结合Android Studio中自动生成的类来看,效果要好于查看贴出来的代码。

内部类 Default

public static class Default implements com.renj.service.aidl.IRemoteBookBinder 
    @Override
    public void addBookIn(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException 
    

    @Override
    public void addBookOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException 
    

    @Override
    public void addBookInOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException 
    

    @Override
    public java.util.List<com.renj.service.bean.BookBean> getBookList() throws android.os.RemoteException 
        return null;
    

    @Override
    public android.os.IBinder asBinder() 
        return null;
    

该类实现了 IRemoteBookBinder 类,作为IRemoteBookBinder接口的默认实现,在实际中并无太多实际意义。

IRemoteBookBinder.aidl 文件中声明的方法

public void addBookIn(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException;

public void addBookOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException;

public void addBookInOut(com.renj.service.bean.BookBean bookBean) throws android.os.RemoteException;

public java.util.List<com.renj.service.bean.BookBean> getBookList() throws android.os.RemoteException;

这几个方法都是在 IRemoteBookBinder.aidl 进行了声明的,直接作为接口的声明方法,具体有子类实现。

内部类 Stub

public static abstract class Stub extends android.os.Binder implements com.renj.service.aidl.IRemoteBookBinder 
    private static final java.lang.String DESCRIPTOR = "com.renj.service.aidl.IRemoteBookBinder";


    public Stub() 
        this.attachInterface(this, DESCRIPTOR);
    

    public static com.renj.service.aidl.IRemoteBookBinder asInterface(android.os.IBinder obj) 
        if ((obj == null)) 
            return null;
        
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.renj.service.aidl.IRemoteBookBinder))) 
            return ((com.renj.service.aidl.IRemoteBookBinder) iin);
        
        return new com.renj.service.aidl.IRemoteBookBinder.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 TRANSACTION_addBookIn: 
                data.enforceInterface(descriptor);
                com.renj.service.bean.BookBean _arg0;
                if ((0 != data.readInt())) 
                    _arg0 = com.renj.service.bean.BookBean.CREATOR.createFromParcel(data);
                 else 
                    _arg0 = null;
                
                this.addBookIn(_arg0);
                reply.writeNoException();
                return true;
            
            case TRANSACTION_addBookOut: 
                data.enforceInterface(descriptor);
                com.renj.service.bean.BookBean _arg0;
                _arg0 = new com.renj.service.bean.BookBean();
                this.addBookOut(_arg0);
                reply.writeNoException();
                if ((_arg0 != null)) 
                    reply.writeInt(1);
                    _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                 else 
                    reply.writeInt(0);
                
                return true;
            
            case TRANSACTION_addBookInOut: 
                // ...
                return true;
            
            case TRANSACTION_getBookList: 
                data.enforceInterface(descriptor);
                java.util.List<com.renj.service.bean.BookBean> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            
            default: 
                return super.onTransact(code, data, reply, flags);
            
        
    

    private static class Proxy implements com.renj.service.aidl.IRemoteBookBinder 
        // ...
    

    static final int TRANSACTION_addBookIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addBookOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    static final int TRANSACTION_addBookInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);

    public static boolean setDefaultImpl(com.renj.service.aidl.IRemoteBookBinder impl) 
        if (Stub.Proxy.sDefaultImpl == null && impl != null) 
            Stub.Proxy.sDefaultImpl = impl;
            return true;
        
        return false;
    

    public static com.renj.service.aidl.IRemoteBookBinder getDefaultImpl() 
        return Stub.Proxy.sDefaultImpl;
    

以上代码,去掉了 Proxy 内部类的实现,在后面再说,然后就是去掉了 TRANSACTION_addBookInOut 部分的实现,因为这部分就是TRANSACTION_addBookInTRANSACTION_addBookOut的结合。

Stub 类继承了 android.os.Binder 并且实现了 IRemoteBookBinder 接口,她就是真正的Binder类,是在Service中的onBind()方法返回给客户端的Binder类(一般我们在onBind()方法中返回的都是 .Stub 的子类,如 RemoteBookBinderImpl extends IRemoteBookBinder.Stub),也是客户端 ServiceConnection 回调方法 onServiceConnected() 中的参数 service

private ServiceConnection bindConnection = new ServiceConnection() 
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) 
        iRemoteBookBinder = IRemoteBookBinder.Stub.asInterface(service);
    

    @Override
    public void onServiceDisconnected(ComponentName name) 
    
;

DESCRIPTOR

Binder 的唯一标识,一般使用当前类的全路径名表示,如上面的 “com.renj.service.aidl.IRemoteBookBinder”。

构造方法

将本身对象(this)和唯一标识(DESCRIPTOR)通过 attachInterface() 传递给父类 Binder 类,这里需要注意,后面会有地方用到。

public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) 
    mOwner = owner;
    mDescriptor = descriptor;

asInterface(android.os.IBinder obj)

将服务端的Binder对象转换成客户端所需要的AIDL接口对象, ServiceConnection 回调方法 onServiceConnected() 中就是使用这个方法对 service 参数进行转换。这个方法内部的转换是区分进程的,如果客户端和服务端在同一个进程,那么返回的就是服务端的Stub对象本身,否则返回的就是系统封装后的 IRemoteBookBinder.Stub.Proxy 类。注意创建 IRemoteBookBinder.Stub.Proxy(obj) 对象时,参数 obj ,就是 onBind() 方法返回的Binder对象,也就是回调 onServiceConnected() 方法中的参数 service。

asInterface()方法内部是通过 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 方法判断客户端和服务端是否在同一进程的,queryLocalInterface() 方法在 Binder 中的实现:

public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) 
    if (mDescriptor != null && mDescriptor.equals(descriptor)) 
        return mOwner;
    
    return null;

上述方法中的 mDescriptormOwner 就是通过 Stub 的构造方法设置的值。

asBinder() 方法

该方法返回当前的类对象,也就是Binder

方法id

static final int TRANSACTION_addBookIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBookOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_addBookInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);

这里就是aidl接口中的方法,为他们都声明一个id,用于在 onTransact() 方法中确定调用的是哪个方法。

onTransact() 方法

该方法运行在服务端的Binder线程池中,当客户端跨进程调用服务器方法时,远程请求会通过系统的封装(Proxy)来调用该方法,然后由该方法根据方法id调用对应的方法,该方法的声明为:

boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
  • code:调用的方法id
  • data:传递进来的数据
  • reply:需要返回的数据
  • flags:额外标记,正常传0(1:单向RPC,无返回值)
  • 返回值:Boolean类型,true表示调用成功,false表示调用失败,可以在这个方法内做权限控制。

我们在《Android Service aidl使用及进阶》说过,非基本数据类型的数据的走向标记in、out、inout的作用,这里再次进行说明一下,因为以下内容需要明白他们的作用才能更好的理解。

  • in:服务端只能接收输入,不能输出给客户端。当客户端传递BookBean给服务端时,服务端能获取到BookBean对象信息,但是服务端进行修改之后,客户端不会发生改变,还是原来的。

  • out:服务端不能接收输入,只能输出给客户端。当客户端传递BookBean给服务端时,服务端获取到的BookBean对象之后,并不能获取BookBean里面的字段信息(都是默认值),但是服务端修改了这些值后,客户端的信息会发生改变,变成服务端修改之后的值。

  • inout:服务端既能接收输入,也能输出给客户端。当客户端传递BookBean给服务端时,服务端能获取到BookBean对象信息;服务端修改了这些值后,客户端的信息也会发生改变,变成服务端修改之后的值。

下面对onTransact()内对方法 addBookIn()addBookOut() 方法具体说明:

  • TRANSACTION_addBookIn

    表示调用的是 addBookIn() 方法,该方法在 IRemoteBookBinder.aidl 中的定义是 void addBookIn(in BookBean bookBean);,注意关键字 in

      data.enforceInterface(descriptor); // 服务端标识,这个数据该由谁处理
      com.renj.service.bean.BookBean _arg0; // 声明一个新的对象
      if ((0 != data.readInt()))  // 判断data是否有数据需要读取
      	// 根据传递进来的data创建一个新的对象并赋值给 _arg0
          _arg0 = com.renj.service.bean.BookBean.CREATOR.createFromParcel(data);
       else 
          _arg0 = null;
      
      this.addBookIn(_arg0); // 调用我们定义方法,具体由子类实现,将新的对象 _arg0 作为参数传递给方法
      reply.writeNoException(); // 没有发生异常
      return true; // 返回成功
    

    当确定调用的 addBookIn() 方法,先将 Binder 的唯一标识对数据标记,然后服务器创建一个新的对象,因为使用了 in 关键字,所以需要读取老的数据,然后调用抽象 addBookIn() 方法(由子类实现),并且将新建的对象作为参数传递,并没有将新的数据写入到 reply 中,因为没有 out 关键字。

  • TRANSACTION_addBookOut

    表示调用的是 addBookOut() 方法,该方法在 IRemoteBookBinder.aidl 中的定义是 void addBookOut(out BookBean bookBean);,注意关键字 out

      data.enforceInterface(descriptor); // 服务端标识,这个数据该由谁处理
      com.renj.service.bean.BookBean _arg0; // 声明一个新的对象
      _arg0 = new com.renj.service.bean.BookBean(); // 因为只有out关键字,所以不会传递老的对象中的数据
      this.addBookOut(_arg0); // 调用我们定义的方法,传递新的对象,所有字段都是默认值
      reply.writeNoException(); // 没有发生异常
      if ((_arg0 != null)) 
          reply.writeInt(1); // 当数据发送变化后,修改标记
      	// 把变化后的数据写入 reply 中
          _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); 
       else 
          reply.writeInt(0);
      
      return true; // 返回成功
    

    当确定调用的 addBookOut() 方法,先将 Binder 的唯一标识对数据标记,然后服务器创建一个新的对象,因为使用了 out 关键字,所以只会创建一个包含默认值的东西,而不会读取老的数据,然后调用抽象 addBookOut() 方法(由子类实现),并且将新建的对象作为参数传递,方法调用完成后,就修改的数据写入 reply 中。

    对于IRemoteBookBinder.aidl 中的定义的 addBookInOut() 方法,也差不多。只是有 in 和 out 两个关键字,所以既能传入原始数据,也会返回修改后的数据。

  • TRANSACTION_getBookList

    表示调用的是 getBookList() 方法,该方法在 IRemoteBookBinder.aidl 中的定义是 List<BookBean> getBookList();

      data.enforceInterface(descriptor);
      java.util.List<com.renj.service.bean.BookBean> _result = this.getBookList();
      reply.writeNoException();
      reply.writeTypedList(_result);
    

    这个方法直接调用抽象 getBookList() 方法(由子类实现),然后将结果写入到 reply 中返回。

setDefaultImpl()getDefaultImpl() 方法

setDefaultImpl() 方法是设置默认aidl接口实现,当远程调用 transact() 方法(实际最终调用的就是内部类 Stub 的 onTransact() 方法)中的对应方法,返回结果为false,并且通过 getDefaultImpl() 方法获取到的默认实现不为 null 就会调用默认实现的对应方法。

内部类 Stub 的内部类 Proxy

private static class Proxy implements com.renj.service.aidl.IRemoteBookBinder 
        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;
        

        @Override
        public void addBookInOut(com.renj.service.bean.BookBean bookBean) 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 ((bookBean != null)) 
                    _data.writeInt(1);
                    bookBean.writeToParcel(_data, 0);
                 else 
                    _data.writeInt(0);
                
                boolean _status = mRemote.transact(Stub.TRANSACTION_addBookInOut, _data, _reply, 0);
                if (!_status && getDefaultImpl() != null) 
                    getDefaultImpl().addBookInOut(bookBean);
                    return;
                
                _reply.readException();
                if ((0 != _reply.readInt())) 
                    bookBean.readFromParcel(_reply);
                
             finally 
                _reply.recycle();
                _data.recycle();
            
        

        @Override
        public java.util.List<com.renj.service.bean.BookBean> getBookList() throws android.os.RemoteException 
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.renj.service.bean.BookBean> _result;
            try 
                _data.writeInterfaceToken(DESCRIPTOR);
                boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                if (!_status && getDefaultImpl() != null) 
                    return getDefaultImpl().getBookList();
                
                _reply.readException();
                _result = _reply.createTypedArrayList(com.renj.service.bean.BookBean.CREATOR);
             finally 
                _reply.recycle();
                _data.recycle();
            
            return _result;
        

        public static com.renj.service.aidl.IRemoteBookBinder sDefaultImpl;
    

首先使用 Proxy 的情况是因为我们在 StubasInterface() 方法中判断了客户端和服务端是不是在同一个进程,不是在同一个进程时,才返回 Proxy 对象。

构造方法

在解析 asInterface() 方法时,就说明了Proxy 的构造方法需要一个参数 obj ,就是 onBind() 方法返回的Binder对象,也就是回调 onServiceConnected() 方法中的参数 service。

asBinder()getInterfaceDescriptor()

  • asBinder()方法返回通过构造方法传递进来的 Binder 对象
  • getInterfaceDescriptor()方法返回 Stub 的 DESCRIPTOR 字段,Binder 的唯一标识。

方法调用

就是当我们在客户端通过 service 调用方法时,实际上调用的就是 Proxy 中的方法,理由通过前面的分析我们已经知道了(对asInterface(android.os.IBinder obj) 方法分析)。

对于 Stub 类,我们分析了 addBookIn()addBookOut() 方法,那么在 Proxy 类中,我们分析一下 addBookInOut() 方法。

  • void addBookInOut(com.renj.service.bean.BookBean bookBean) 方法

      android.os.Parcel _data = android.os.Parcel.obtain();  // 创建参数对象
      android.os.Parcel _reply = android.os.Parcel.obtain(); // 创建返回值对象
      try 
          _data.writeInterfaceToken(DESCRIPTOR); // 唯一标识
          if ((bookBean != null))  // 参数不为null时,将客户端传递的参数数据写到新建对象中(因为关键字为 inout)
              _data.writeInt(1);
              bookBean.writeToParcel(_data, 0);
           else 
              _data.writeInt(0);
          
      	// 通过 mRemote 调用 transact() 方法,传递调用方法id(Stub中定义的方法id),输入输出对象和标志位,
      	// 这些参数就时 Stub#onTransact()方法所接收的参数
          boolean _status = mRemote.transact(Stub.TRANSACTION_addBookInOut, _data, _reply, 0);
      	// Stub#onTransact()方法返回失败,并且设置了默认aidl接口实现,就调用默认的实现
          if (!_status && getDefaultImpl() != null) 
              getDefaultImpl().addBookInOut(bookBean);
              return;
          
          _reply.readException();
      	// 因为有是 inout 关键字,所以需要将修改后的结果重写更新到客户端的对象中
          if ((0 != _reply.readInt())) 
              bookBean.readFromParcel(_reply);
          
       finally 
          _reply.recycle();
          _data.recycle();
      
    

    上面的代码中,已经对代码做了注释了,我们发现在 Proxy 中的方法内,其实就是对参数和结果数据的处理,并没有其他的逻辑,然后直接调用了 mRemotetransact() 方法,mRemote 就是 Binder,这个我们是知道的,那看看 Binder#transact() 方法做了什么操作了

      public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
              int flags) throws RemoteException 
          if (false) Log.v("Binder", "Transact: " + code + " to " + this);
    
          if (data != null) 
              data.setDataPosition(0);
          
      	// 调用了 onTransact 方法
          boolean r = onTransact(code, data, reply, flags);
          if (reply != null) 
              reply.setDataPosition(0);
          
          return r;
      
    

    Binder#transact() 中调用了 onTransact() 方法,这个方法也就是我们刚刚在分析 Stub 类时分析的方法。到了这里,我们就已经知道了客户端在跨进程调用服务端方法时的流程,以及 in、out和inout关键字作用的原理了。

  • java.util.List<com.renj.service.bean.BookBean> getBookList() 方法

      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      java.util.List<com.renj.service.bean.BookBean> _result;
      try 
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) 
              return getDefaultImpl().getBookList();
          
          _reply.readException();
      	// 将返回 _reply 对象封装成客户端需要的类型
          _result = _reply.createTypedArrayList(com.renj.service.bean.BookBean.CREATOR);
       finally 
          _reply.recycle();
          _data.recycle();
      
      return _result;
    

    addBookInOut() 方法类似,封装参数,然后调用mRemotetransact() 方法,最后将结果 _reply 对象封装成客户端需要的类型返回。

最后放一张图表示跨进程调用的过程,如果了解了上面的内容,应该能够很容易看明白。

以上是关于Android Service aidl分析的主要内容,如果未能解决你的问题,请参考以下文章

Android Framework实战开发-Binder专题讲解之aidl文件的详细分析

Android Framework实战开发-Binder专题讲解之aidl文件的详细分析

Android Service 流程分析

android 四大组件之Service(10) AIDL android interface definition language

基于AIDL编程实现Android远程Service服务

基于AIDL编程实现Android远程Service服务