“我正在参与「启航计划」”

接着上篇文章重学Binder进程间通讯-原理篇,前面咱们介绍过,Binder 是依据 C/S 架构的,由 Client、Server、ServiceManager、Binder 驱动组成。其间 Client、Server、ServiceManager运转在用户空间,Binder驱动运转在内核空间。其间ServiceManager 和 Binder 驱动由体系供给,而 Client、Server 由应用程序完结。

重学Binder进程间通信-aidl篇

Client、Server 经过调用binder驱动为应用层供给了open(),mmap(),poll(),ioctl()办法来拜访设备问价 /dev/binder.open()担任打开驱动,mmap()担任对binder做内核空间向用户空间的地址映射,ioctl()担任binder协议的通讯。Binder底层再调用 binder_open()、binder_mmap()、binder_ioctl()来完结进程间通讯。

重学Binder进程间通信-aidl篇

Binder进程间通讯的进程

重学Binder进程间通信-aidl篇

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端拜访的一个流程通常如下:

重学Binder进程间通信-aidl篇

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

重学Binder进程间通信-aidl篇

至此,咱们经过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文件的。

重学Binder进程间通信-aidl篇

aidl的本质效果如下:

重学Binder进程间通信-aidl篇

在编码完结跨进程调用之前,咱们先要了解下用到的一些类(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 文件,如下图:

重学Binder进程间通信-aidl篇

看看体系给咱们生成的 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 机制浅析