重学Binder进程间通信-aidl篇

“我正在参加「掘金·启航计划」”

接着上篇文章重学Binder进程间通信-原理篇,前面我们介绍过,Binder 是基于 C/S 架构的,由 Client、Server、ServiceManager、Binder 驱动组成。其中 Client、Server、ServiceManager运行在用户空间,Binder驱动运行在内核空间。其中ServiceManager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序实现。

image.png

Client、Server 通过调用binder驱动为应用层提供了open(),mmap(),poll(),ioctl()方法来访问设备问价 /dev/binder.open()负责打开驱动,mmap()负责对binder做内核空间向用户空间的地址映射,ioctl()负责binder协议的通信。Binder底层再调用 binder_open()、binder_mmap()、binder_ioctl()来实现进程间通信。

image.png

Binder进程间通信的过程

image.png

Binder跨进程通信有两种方式,如上图

  1. ServiceManager的getService获取Manager进行Binder通信
  2. Service组件的binderService方式进行通信

利用ServiceManager的getService获取Manager进行Binder通信

ServiceManager相当于一个DNS服务器,我们从DNS服务器中获取对应的服务,也就是获取到我们需要的Manager,本质上Manager还是IBinder。比如我们想获取电池电量,此时我们就可以用 getSystemService()来获取电池电量,代码如下:

//获取系统注册服务的跨进程通信方式
BatteryManager manager = (BatteryManager) getSystemService(BATTERY_SERVICE);
manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE);
manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW);

利用Service组件的binderService方式进行通信

我们知道,在Web端访问的一个流程通常如下:

image.png

没错,Binder的机制和此非常相似,它也是一套基于CS的架构。如web的访问过程,Binder也有四个重要的角色:Binder Server、Binder Client、Service Manager、Binder驱动。其和web端的对应关系如下:

image.png

至此,我们通过Web访问请求大致能总结出 Binder 通信过程:

  1. 进程Server 通过 Binder驱动 向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
  2. Client 通过唯一标识 在 Binder驱动 的帮助下 从ServiceManager 中获取到对应的 Binder实体的引用,通过这个引用就能实现和 Server 进程的通信。

Binder代码浅析

我们在实际开发中,实现进程间通信最多的就是 AIDL。当我们定义好 AIDL 文件时,在编译阶段编译器会帮我们生成相应的 Java 代码实现 IPC 通信,既然用到了 AIDL,自然而然就衍生出一些问题。

什么是 aidl?为什么要设计 aidl 这门语言?

AIDL是缩写,全称是:Android Interface Definition Language,Android 接口定义语言(AIDL),你可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信(IPC) 进行互相通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。

aidl的优势有两方面:

  1. aidl简化代码量,如果用java实现需要写很多重复固定的代码,代码量庞大,容易出现错误
  2. aidl可以通过脚本的配置文件生成固定的java代码

aidl生成java文件是通过 android sdk 中的aidl脚本文件(window是aidl.exe)来生成Java文件的。

image.png

aidl的本质作用如下:

image.png

在编码实现跨进程调用之前,我们先要了解下用到的一些类(IBinder/IInterface/Binder/BinderProxy/Stub)。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。

  • IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
  • IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
  • Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
  • Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

源码分析

以一个简单的aidl为例,我们新建一个aidl文件IEegetsInterface,如下:

import com.example.servicedemo.binder.bean.StudentBean;

interface IEegetsInterface {


    List<StudentBean> getStudents();

    void addStudents(out StudentBean studentBean);
}

新建完成后,我们执行编译,会在 app/build 下生成对应的 IEegetsInterface.java 文件,如下图:

image.png

看看系统给我们生成的 IEegetsInterface.java 源码:


public interface IEegetsInterface extends android.os.IInterface
{
  /** Default implementation for IEegetsInterface. */
  public static class Default implements com.example.servicedemo.IEegetsInterface
  {

    @Override public java.util.List<com.example.servicedemo.binder.bean.StudentBean> getStudents() throws android.os.RemoteException
    {
      return null;
    }
    @Override public void addStudents(com.example.servicedemo.binder.bean.StudentBean studentBean) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** 「1」静态抽象内部类 Stub 继承自 Binder 同时实现接口的方法,用于构造服务端的IBinder对象 */
  public static abstract class Stub extends android.os.Binder implements com.example.servicedemo.IEegetsInterface
  {
    //「1-1」Service唯一的id
    private static final java.lang.String DESCRIPTOR = "com.example.servicedemo.IEegetsInterface";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      //「1-2」用于将 `IEegetsInterface` 接口和 Binder 通过 `DESCRIPTOR` 唯一性进行关联
      this.attachInterface(this, DESCRIPTOR);
    }
    /** 「1-3」根据 service的唯一Id通过 `queryLocalInterface` 获取相应的的Interface对象,如果查找不到则会拿`IBinder`对象obj创建一个代理对象Proxy */
    public static com.example.servicedemo.IEegetsInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.servicedemo.IEegetsInterface))) {
        return ((com.example.servicedemo.IEegetsInterface)iin);
      }
      return new com.example.servicedemo.IEegetsInterface.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    //接收整个系统的调用,客户端发起请求,通过 Binder内部的驱动处理,系统会调用到  onTransact,从而匹配到对应的接口,最终调用服务端 Service 里的接口方法
    @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_getStudents:
        {
          data.enforceInterface(descriptor);
          java.util.List<com.example.servicedemo.binder.bean.StudentBean> _result = this.getStudents();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        case TRANSACTION_addStudents:
        {
          data.enforceInterface(descriptor);
          com.example.servicedemo.binder.bean.StudentBean _arg0;
          _arg0 = new com.example.servicedemo.binder.bean.StudentBean();
          this.addStudents(_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;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    //客户端的代理类,实现接口的方法
    private static class Proxy implements com.example.servicedemo.IEegetsInterface
    {
      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 java.util.List<com.example.servicedemo.binder.bean.StudentBean> getStudents() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.example.servicedemo.binder.bean.StudentBean> _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getStudents, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getStudents();
          }
          _reply.readException();
          _result = _reply.createTypedArrayList(com.example.servicedemo.binder.bean.StudentBean.CREATOR);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      @Override public void addStudents(com.example.servicedemo.binder.bean.StudentBean studentBean) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_addStudents, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().addStudents(studentBean);
            return;
          }
          _reply.readException();
          if ((0!=_reply.readInt())) {
            studentBean.readFromParcel(_reply);
          }
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.example.servicedemo.IEegetsInterface sDefaultImpl;
    }
    static final int TRANSACTION_getStudents = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addStudents = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.example.servicedemo.IEegetsInterface impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.example.servicedemo.IEegetsInterface getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public java.util.List<com.example.servicedemo.binder.bean.StudentBean> getStudents() throws android.os.RemoteException;
  public void addStudents(com.example.servicedemo.binder.bean.StudentBean studentBean) throws android.os.RemoteException;
}

Stub

当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 AIDL 接口对象,这个 IBinder 类型的入参 binder 是驱动传给我们的。

从 AIDL 生成的 Java 类我们可以看出,Stub 继承自 Binder 同时实现接口的方法,用于构造服务端的IBinder对象,意味着 Stub 自己本身就是一个 Binder 对象,并且实现IEegetsInterface接口(接口本身是IInterface),因此 Stub 有客户端需要的方法,也就是(addStudents 和 getStudents),

attachInterface

private static final java.lang.String DESCRIPTOR = "com.example.servicedemo.IEegetsInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
  this.attachInterface(this, DESCRIPTOR);
}

用于将 IEegetsInterface 接口和 Binder 通过 DESCRIPTOR 唯一性进行关联。我们可以简单理解为该方法会将(descriptor,owner)作为(key,value)存入Binder对象中的一个Map对象中,Binder对象可通过attachInterface方法持有一个IInterface对象(即owner)的引用,并依靠它获得完成特定任务的能力。queryLocalInterface方法可以认为是根据key值(即参数 descriptor)查找相应的IInterface对象。

asInterface

public static com.example.servicedemo.IEegetsInterface asInterface(android.os.IBinder obj)
{

  if ((obj==null)) {
    return null;
  }
  android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  if (((iin!=null)&&(iin instanceof com.example.servicedemo.IEegetsInterface))) {
    return ((com.example.servicedemo.IEegetsInterface)iin);
  }
  return new com.example.servicedemo.IEegetsInterface.Stub.Proxy(obj);
}

根据 service的唯一Id(DESCRIPTOR)通过 queryLocalInterface 获取相应的的Interface对象,queryLocalInterface方法可以认为是根据key值(即参数 descriptor)查找相应的IInterface对象。如果查找不到则会拿IBinder对象obj创建一个代理对象Proxy。

Proxy

private static class Proxy implements com.example.servicedemo.IEegetsInterface
{

//Stub 传递的IBinder对象
  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 java.util.List<com.example.servicedemo.binder.bean.StudentBean> getStudents() throws android.os.RemoteException
  {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.util.List<com.example.servicedemo.binder.bean.StudentBean> _result;
    try {
      _data.writeInterfaceToken(DESCRIPTOR);
      boolean _status = mRemote.transact(Stub.TRANSACTION_getStudents, _data, _reply, 0);
      if (!_status && getDefaultImpl() != null) {
        return getDefaultImpl().getStudents();
      }
      _reply.readException();
      _result = _reply.createTypedArrayList(com.example.servicedemo.binder.bean.StudentBean.CREATOR);
    }
    finally {
      _reply.recycle();
      _data.recycle();
    }
    return _result;
  }
  @Override public void addStudents(com.example.servicedemo.binder.bean.StudentBean studentBean) throws android.os.RemoteException
  {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
      _data.writeInterfaceToken(DESCRIPTOR);
      boolean _status = mRemote.transact(Stub.TRANSACTION_addStudents, _data, _reply, 0);
      if (!_status && getDefaultImpl() != null) {
        getDefaultImpl().addStudents(studentBean);
        return;
      }
      _reply.readException();
      if ((0!=_reply.readInt())) {
        studentBean.readFromParcel(_reply);
      }
    }
    finally {
      _reply.recycle();
      _data.recycle();
    }
  }
  public static com.example.servicedemo.IEegetsInterface sDefaultImpl;
}

在 Proxy 中的 getStudents()addStudents() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。从代码我们知道 Proxy 是在 Stub 的 asInterface 中创建,即这里的 remote 是个 BinderProxy 对象。最终通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 IEegetsInterface 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数);我们这个例子里面,调用了 Binder 本地对象的 getStudents()addStudents() 并将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。

transact

Binder.onTransact()是为Binder.transact()的调用而准备的,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);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

此段代码位于android/os/Binder.java

首先,在调用Binder.onTransact()之前及之后,分别对请求结构的引用及返回结构的引用重置读写position,以及调用Binder.onTransact()。在此提醒,Binder.transact()的调用者是Stub下的内部类Proxy中的各个.aidl中定义的方法。

最终,千辛万苦地,终于来到了.aidl自行生成实现的Binder.onTransact()方法了。特别的是,有两个地方值得去注意:

  • 其一,在最终的开发中,将会继承抽象类Stub,并实现所有在.aidl中定义的方法。这些具体方法的直接调用者,正是当前我们所在的onTransact()方法;
  • 其二,正是基于上面一条,可以得知:无论远程调用者(Client)身处何方,最终,一定会经过此处的onTransact()方法,并由onTransact()直接调用目标方法。

要知道,传入onTransact()方法的参数中,拥有目标方法的ID、指向参数的引用,以及指向返回结果的引用。所有远程调用者(Client)想要做的事,都通过层层调用及参数包装汇聚到onTransact(),再由onTransact()分发到真正的目标方法执行。

AIDL关键字

oneway修饰符

/*

* IEegetsInterface.aidl

*/

interface IChangeCallback {

   int changeData(int changeIndex); //同步调用
   oneway void changeData(int changeIndex); //异步调用
}

oneway关键字用于修饰远程调用的行为,被oneway修饰的方法不能有返回值。也不可以带有inout 的参数。

oneway 修饰的方法支持异步回调,它只是发送事物数据并立即返回,接口的实现最终调用此实现时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用来接收。

in、out、inout

/*

* IEegetsInterface.aidl

*/

interface IChangeCallback {

   String getConvertName(in StudentInfo info);
   void getServiceStudentInfo(out StudentInfo serviceInfo);
   void getServiceStudentInfoInOut(inout StudentInfo serviceInfo);
}

通信中的数据流向。

  • in: 数据只能由客户端流向服务端(表现为服务端修改参数对象,并不会影响客户端。服务端->客户端)
  • out: 数据只能由服务端流向客户端(表现为服务端收到的参数是空对象,服务端修改参数对象,客户端也会同步变化。服务端->客户端
  • inout: 表示数据可在服务端和客户端双向流动(表现为服务端可以接收到客户端传递的对象,并且服务端修改数据客户端也会同步改变。服务端<->客户端

参考资料:

Binder学习指南

Binder 总体架构及相关代码浅析

Binder:为什么要通过onTransact()调用目标方法

Android 进阶8:进程通信之 Binder 机制浅析

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYp42QHK' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片