IPC通信解析

893 阅读23分钟

一、IPC通信总览

IPCInter-Process Communication (进程间通信)。

Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能直接操作对方的数据,这叫做“进程隔离”。

进程隔离是为保护操作系统中进程互不干扰而设计的一组不同硬件和软件的技术。进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就防止进程A将数据信息写入进程B

跨进程通信要求:把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。然后,返回值将沿相反方向传输回来。

Android中,常用的进程通信机制有:文件、AIDL(基于Binder)、BinderMessenger(基于Binder)、ContentProvider(基于Binder)、Socket

这里重点看一下BinderAIDL

二、Binder解析

2.1 Binder理解

BinderAndroid 为我们提供的 IPC方式。

从JAVA层面理解Binder其实就是一个实现了IBinder接口的类

从Linux内核层理解,可以将 Binder 理解为一个虚拟的物理设备,而这个设备的启动、进程间数据的传输都要依赖于一个叫做 Binder DriverBinder 驱动)的东西。

Android应用层理解Binder是客户端和服务端通信的媒介。

知识概念

这里先理解一下用户空间等相关概念。

  • 用户空间/内核空间

    Linux Kernel是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。

    对于Kernel这种高安全级别的东西,不容许其它的应用程序随便调用或访问的,需要对Kernel提供一定的保护机制,来告知应用程序哪些资源可访问,哪些拒绝访问。将Kernel和上层的应用程序抽像地隔离开,分为内核空间和用户空间

  • 系统调用/内核态/用户态

    用户空间有时需要访问内核的资源,例如应用程序访问文件。

    用户空间访问内核空间的唯一方式是系统调用。通过该接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,保障系统的安全和稳定性。

    当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核态。当进程执行用户自己的代码时,称为用户态

  • 内核模块/驱动

    通过系统调用,用户空间可以访问内核空间。如果一个用户空间想和另一个用户空间通信,可以让操作系统内核添加支持,例如Socket、管道等。

    Binder不是Linux内核的一部分,其通过Linux的动态可加载内核模块(LKM)机制解决该问题。

    模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。Android系统可以通过添加一个内核模块运行在内核空间,用户进程之间通过这个模块为桥梁完成通信。

    Android中,这个运行在内核空间,负责各个用户进程通过Binder通信的内核模块叫做Binder驱动。

    驱动就是操作硬件的接口。为了支持Binder通信过程,Binder使用了一种“硬件”,因此这个模块被称之为驱动。

2.2 Binder 通信模型

对于跨进程通信的双方,一般称之为Server进程(服务端)、Client进程(客户端)。

日常的电话通信过程,完成通信过程需要两个角色:通信录和基站。对于BinderBinder驱动功能类似基站,而通讯录的功能由ServiceManager实现。

通信步骤主要如下所示:

  1. SM建立(建立通信录)

    首先有一个进程向驱动提出申请为SM;驱动同意之后,SM进程负责管理Service

  2. 各个ServerSM注册(完善通信录)

    每个Server端进程启动之后,向SM报告名字地址,这样SM就建立了一张通信录表

  3. 地址查询

    Client想要与Server通信,首先询问SMSM返回Server的通信地址等信息。Client收到后,利用该地址完成通信

2.3 Binder原理

通过Binder通信模型,知道通信过程有四个角色:ClientServerServiceManagerdriver。看一下ClientServer实际是如何完成通信。

内核可以访问AB的所有数据,所以最简单的方式是通过内核做中转

假设进程A要给进程B发送数据,先把A的数据copy到内核空间,然后把内核空间对应的数据copyB就完成了。用户空间要操作内核空间,需要通过系统调用,这里提供两个系统调用:copy_from_user, copy_to_user

上述这种IPC通信方式存在两个问题:

  1. 性能低下

    一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝

  2. 时间与空间资源浪费

    接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

内存映射

所有的数据都存储在物理内存中,而进程访问内存只能通过虚拟地址,如果想成功访问必须得有个前提:虚拟地址和物理内存之间建立映射关系。

实现前述的映射关系,进程就可以采用指针的方式来读写操作这一段内存,进而完成对文件(或者其它被映射的对象)的操作,而不必再调用 read/write 等系统调用函数了。内存映射的该特点可以减少数据拷贝的次数,实现用户空间和内核空间的高效互动。

若想让A进程通过(用户空间)虚拟地址访问到B进程中的数据,最高效的方式就是修改A/B进程中某些虚拟地址的PTE,使得这些虚拟地址映射到同一片物理区域。这样就不存在任何拷贝,因此数据在物理空间中也只有一份。

共享内存虽然高效,但是由于物理内存只有一份,不得不考虑各种同步问题。内存拷贝的方法可以保证不同进程都拥有一块属于自己的数据区域,该区域不用考虑进程间的数据同步问题。

Android中的Binder是基于速度和安全性考虑后的选择。

Binder IPC 基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。

比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间);通常在这种场景下 mmap() 就能发挥作用。通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间

Binder通信原理

一次完整的Binder 通信过程大致如下:

  1. 首先Binder驱动在内核空间创建一个数据接收缓存区
  2. 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
  3. 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区。由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

结合前述介绍的Binder通信模型,则Binder的通信过程为:

  1. 首先,一个进程使用 BINDER SET CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager
  2. Server 通过驱动向 ServiceManager 中注册 BinderServer 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManagerServiceManger 将其填入查找表
  3. Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

2.4 Binder代理模式

ClientServer 借助 Binder 驱动完成跨进程通信的实现机制。

如果**A 进程想要 B 进程中某个对象(object)要如何实现**?二者分属不同的进程,无法直接使用。

A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy。这个 objectProxy 具有和 object 一模一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。

假设Client进程想要调用Server进程的object对象的一个方法add;对于这个跨进程通信过程,我们来看看Binder机制是如何做的

  1. 首先,Server进程向SM注册,SM建立对应表
  2. 然后ClientSM查询Server。进程之间通信的数据都会经过运行在内核空间里面的驱动,驱动在数据流过的时候做了一点手脚。它并不会给Client进程返回一个真正的object对象,而是返回一个看起来跟object一模一样的代理对象objectProxy,这个objectProxy也有一个add方法,但是这个add方法没有Server进程里面object对象的add方法那个能力;objectProxyadd只是一个傀儡,它唯一做的事情就是把参数包装然后交给驱动
  3. Client获取到objectProxy对象,然后调用add方法。
  4. 驱动收到该消息,发现是这个objectProxy,查表发现其真正要访问的是object对象的add()方法。于是驱动通知Server进程,调用其object对象的add()方法,然后将其结果返回给驱动。
  5. Server进程收到该消息,执行add()操作后返回驱动,驱动将结果返回给Client进程,完成整个通信过程。

Client进程只不过是持有了Server端的代理;代理对象协助驱动完成了跨进程通信。

结合前述内容,对Binder进行二次理解:

  • Binder指的是一种通信机制。我们说AIDL使用Binder进行通信,指的就是Binder这种IPC机制。

  • 对于Server进程来说,Binder指的是**Binder本地对象**

  • 对于Client来说,Binder指的是**Binder代理对象**,它只是**Binder本地对象**的一个远程代理。

    对这个Binder代理对象的操作,会通过驱动最终转发到Binder本地对象上去完成。对于一个拥有Binder对象的使用者而言,它无须关心这是一个Binder代理对象还是Binder本地对象。对于代理对象的操作和对本地对象的操作对它来说没有区别。

  • 对于传输过程而言,Binder是可以进行跨进程传递的对象。Binder驱动会对具有跨进程传递能力的对象做特殊处理,自动完成代理对象和本地对象的转换。

Server进程里面的Binder对象指的是Binder本地对象,Client里面的对象值得是Binder代理对象

Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换,因此Binder驱动必然保存了每一个跨越进程的Binder对象的相关信息。在驱动中,Binder本地对象的代表是一个叫做binder_node的数据结构,Binder代理对象是用binder_ref代表的,有的地方把Binder本地对象直接称作Binder实体,把Binder代理对象直接称作Binder引用(句柄),其实指的是Binder对象在驱动里面的表现形式。

三、AIDL 解析

实际开发时,实现进程间通信的方法一般是通过AIDL。当我们定义好 AIDL 文件,在编译时编译器会帮我们生成代码实现 IPC 通信。

这里通过借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。

AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。

仅仅是跨进程通信,还可以选择BraodcastReceiverMessenger等,但是前者占用系统资源较多,后者无法并发执行,在多线程下不适用,此时就可以用AIDL实现。

3.1 基本语法

AIDL文件的后缀是 .aidl,存放在名为aidl文件夹(与java文件夹同级)。

默认情况下AIDL支持下列数据类型:

  • 基础数据类型:bytecharintlongfloatdoubleboolean,其中不支持short类型

  • 支持StringCharSequence

  • 实现了Parcelable接口的数据类型

  • ListMap类型,其承载的数据必须是AIDL支持的类型

定向tag

除以上数据类型外,其他类型在使用之前必须通过import导包,即使目标与当前正在编写的 .aidl 文件在同一个包下。

所有非基本数据类型的参数在传递时需要指定一个方向tag来指明数据的流向,可以是inout、或inout,基本数据类型、StringCharSequence默认且只能为in。这三个修饰符被称为定向tag,用于在跨进程通信时指定数据流向。

  • in:表示数据只能由客户端流向服务端
  • out:表示数据只能由服务端流向客户端
  • inout:表示数据可在服务端与客户端之间双向流通

AIDL中的核心类:

  • IBinder

    是一个接口,代表了一种跨进程通信的能力。只要实现了这个接口,就能将这个对象进行传进程传输。这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换

  • IInterface

    代表Server进程具备什么能力。(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)

  • Binder

    Java 层的 Binder 类,代表的其实就是 Binder 本地对象。

    BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。

  • Stub

    AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类。这个类继承了 Binder, 说明它是一个 Binder 本地对象。它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力。Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

3.2 基本使用

整个AIDL通信分为客户端与服务端两个方面:

  • 服务端

    创建一个Service监听客户端连接请求。创建一个AIDL文件,在该文件中声明暴露给客户端的接口,然后在Service中实现这个AIDL接口

  • 客户端

    绑定服务端Service,将服务端返回的IBinder对象转成AIDL接口所属类型,调用AIDL中方法。

服务端执行步骤

1、定义传输数据

首先确定传输的数据。假设这里要传输的数据是User类对象,该对象实现Parcelabel

@Parcelize
data class User(
    var age: Int,
    var name: String
):Parcelable

如果在AIDL中用到了自定义的Parcelable对象,必须新建一个和它同名的AIDL文件,在其中声明它为Parcelable类型。示例用到了User类,所以声明一个User.aidl

// User.aidl
package com.zhewen.aidlserverstudy;

parcelable User;

2、创建AIDL文件,声明暴露的接口

定义完传输的数据类,需要创建AIDL文件,在该文件中声明暴露给客户端的接口

package com.zhewen.aidlserverstudy;
import com.zhewen.aidlServerstudy.User;

interface ICommandServer {
    int add(int value1, int value2);
    List<User> addUser(in User user);
}

自定义Parcelabel对象和AIDL对象必须要显示import进来,不管是否和当前AIDL文件在一个包

声明完暴露给客户端的接口,用编译工具进行编译,则可得到对应的Java实现类

//ICommandServer.java
package com.zhewen.aidlserverstudy;
public interface ICommandServer extends android.os.IInterface
{
  /** Default implementation for ICommandServer. */
  public static class Default implements com.zhewen.aidlserverstudy.ICommandServer
  {
    @Override public int add(int value1, int value2) throws android.os.RemoteException
    {
      return 0;
    }
    @Override public java.util.List<com.zhewen.aidlserverstudy.User> addUser(com.zhewen.aidlserverstudy.User user) throws android.os.RemoteException
    {
      return null;
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.zhewen.aidlserverstudy.ICommandServer
  {
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.zhewen.aidlserverstudy.ICommandServer interface,
     * generating a proxy if needed.
     */
    public static com.zhewen.aidlserverstudy.ICommandServer asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.zhewen.aidlserverstudy.ICommandServer))) {
        return ((com.zhewen.aidlserverstudy.ICommandServer)iin);
      }
      return new com.zhewen.aidlserverstudy.ICommandServer.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 INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
      }
      switch (code)
      {
        case TRANSACTION_add:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          int _arg1;
          _arg1 = data.readInt();
          int _result = this.add(_arg0, _arg1);
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        }
        case TRANSACTION_addUser:
        {
          data.enforceInterface(descriptor);
          com.zhewen.aidlserverstudy.User _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.zhewen.aidlserverstudy.User.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          java.util.List<com.zhewen.aidlserverstudy.User> _result = this.addUser(_arg0);
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.zhewen.aidlserverstudy.ICommandServer
    {
      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 int add(int value1, int value2) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(value1);
          _data.writeInt(value2);
          boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
          if (!_status) {
            if (getDefaultImpl() != null) {
              return getDefaultImpl().add(value1, value2);
            }
          }
          _reply.readException();
          _result = _reply.readInt();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      @Override public java.util.List<com.zhewen.aidlserverstudy.User> addUser(com.zhewen.aidlserverstudy.User user) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.zhewen.aidlserverstudy.User> _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          if ((user!=null)) {
            _data.writeInt(1);
            user.writeToParcel(_data, 0);
          }
          else {
            _data.writeInt(0);
          }
          boolean _status = mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
          if (!_status) {
            if (getDefaultImpl() != null) {
              return getDefaultImpl().addUser(user);
            }
          }
          _reply.readException();
          _result = _reply.createTypedArrayList(com.zhewen.aidlserverstudy.User.CREATOR);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.zhewen.aidlserverstudy.ICommandServer sDefaultImpl;
    }
    static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.zhewen.aidlserverstudy.ICommandServer impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.zhewen.aidlserverstudy.ICommandServer getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public static final java.lang.String DESCRIPTOR = "com.zhewen.aidlserverstudy.ICommandServer";
  public int add(int value1, int value2) throws android.os.RemoteException;
  public java.util.List<com.zhewen.aidlserverstudy.User> addUser(com.zhewen.aidlserverstudy.User user) throws android.os.RemoteException;
}

系统生成该文件后,只需继承其中的Stub抽象类,实现相关方法,在ServiceonBind里返回就实现了AIDL

具体分析一下系统生成的这个Java文件。

public static abstract class Stub extends android.os.Binder implements com.zhewen.aidlserverstudy.ICommandServer {
    //......
    private static class Proxy implements com.zhewen.aidlserverstudy.ICommandServer{
		//......
    }
}

Stub类继承自Binder,即这个Stub是一个Binder本地对象,其实现了ICommandServer接口。ICommandServer接口即我们定义的暴露给客户端的接口,因此其携带客户端需要的能力。

Stub内部有一个内部类Proxy,即Binder代理对象

  • asInterface()

    Client端创建服务端连接时,调用bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中会通过asInterface(android.os.IBinder obj)拿到ICommandServer对象

    /**
         * Cast an IBinder object into an com.zhewen.aidlserverstudy.ICommandServer interface,
         * generating a proxy if needed.
         */
    public static com.zhewen.aidlserverstudy.ICommandServer asInterface(android.os.IBinder obj)
    {
        if ((obj==null)) {
            return null;
        }
        //查找本地Binder对象
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin!=null)&&(iin instanceof com.zhewen.aidlserverstudy.ICommandServer))) {
            return ((com.zhewen.aidlserverstudy.ICommandServer)iin);
        }
        //创建Binder代理对象
        return new com.zhewen.aidlserverstudy.ICommandServer.Stub.Proxy(obj);
    }
    

    函数的参数为IBinder类型对象,这是驱动提供的。根据注释可知,如果是Binder本地对象,其为Binder类型;如果是Binder代理对象,其为BinderProxy类型。

    asInterface()方法主要内容为:先查找Binder本地对象。如果找到,说明ClientServer在同一进程,函数参数为本地对象,可直接强转类型返回。否则就是远程对象,需要创建一个Binder代理对象,实现对远程对象的访问。

  • Proxy

    对于示例中提供的add()addUser()方法,如果ClientServer在同一进程,则可直接调用相关方法。如果是远程调用,需要通过Binder代理完成,即这里的Proxy

    private static class Proxy implements com.zhewen.aidlserverstudy.ICommandServer
    {
        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 int add(int value1, int value2) throws android.os.RemoteException
        {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            int _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                _data.writeInt(value1);
                _data.writeInt(value2);
                boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                if (!_status) {
                    if (getDefaultImpl() != null) {
                        return getDefaultImpl().add(value1, value2);
                    }
                }
                _reply.readException();
                _result = _reply.readInt();
            }
            finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
        @Override public java.util.List<com.zhewen.aidlserverstudy.User> addUser(com.zhewen.aidlserverstudy.User user) throws android.os.RemoteException
        {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.zhewen.aidlserverstudy.User> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                if ((user!=null)) {
                    _data.writeInt(1);
                    user.writeToParcel(_data, 0);
                }
                else {
                    _data.writeInt(0);
                }
                boolean _status = mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
                if (!_status) {
                    if (getDefaultImpl() != null) {
                        return getDefaultImpl().addUser(user);
                    }
                }
                _reply.readException();
                _result = _reply.createTypedArrayList(com.zhewen.aidlserverstudy.User.CREATOR);
            }
            finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
        public static com.zhewen.aidlserverstudy.ICommandServer sDefaultImpl;
    }
    

    看一下其中的add()方法。其首先用Parcel将数据序列化了,然后调用transact()方法。

    Proxy类在asInterface方法中被创建。前述提到驱动返回的IBinder实际是BinderProxy。所以Proxy中的mRemote实际类型为BinderProxy

    看一下BinderProxytransact()方法

    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        //......
        //调用了一个native方法
        return transactNative(code, data, reply, flags);
    }
    

    transact()方法调用了一个native方法,具体实现在frameworks/base/core/jni/android_util_Binder.cpp文件,其进行一系列函数调用,最终调用到了talkWithDriver函数,即将通信过程交给驱动完成。

    这个函数最后通过ioctl系统调用,Client进程陷入内核态。Client调用add方法的线程挂起等待返回。驱动完成一系列操作后唤醒Server进程,调用Server进程本地对象的onTransact函数(实际由Server端线程池完成)

  • onTransact()

    @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;
                }
        }
        switch (code)
        {
            case TRANSACTION_add:
                {
                    data.enforceInterface(descriptor);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            case TRANSACTION_addUser:
                {
                    data.enforceInterface(descriptor);
                    com.zhewen.aidlserverstudy.User _arg0;
                    if ((0!=data.readInt())) {
                        _arg0 = com.zhewen.aidlserverstudy.User.CREATOR.createFromParcel(data);
                    }
                    else {
                        _arg0 = null;
                    }
                    java.util.List<com.zhewen.aidlserverstudy.User> _result = this.addUser(_arg0);
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
            default:
                {
                    return super.onTransact(code, data, reply, flags);
                }
        }
    }
    

    onTransact()主要内容:根据code编号确认Client调用哪个函数(每个AIDL函数都有一个编号,在跨进程的时候,不会传递函数,而是传递编号指明调用哪个函数),然后从data中取出目标方法需要的参数,执行完后之后向reply写入返回值(如果目标方法有返回值),之后驱动唤醒挂起的Client进程里的线程,将结果返回,完成一次跨进程调用。

3、创建Service,实现接口

完成前述操作后,服务端还需创建一个Service,并实现前述暴露给客户端的接口

class CommandService: Service() {
    override fun onBind(p0: Intent?): IBinder? {
        return mRemoteBinder
    }
    
    private val mRemoteBinder:ICommandServer.Stub = object: ICommandServer.Stub() {

        override fun add(value1: Int, value2: Int): Int {
            return CommandServer.sInstance.add(value1,value2)
        }

        override fun addUser(user: User?): MutableList<User> {
            return CommandServer.sInstance.addUser(user)
        }
}

客户端执行步骤

1、文件拷贝

将服务端aidl文件复制到客户端与java同级的目录下,保持目录结构一致。如果有传输自定义的Parcelabel对象,需要将该类拷贝到相应的包名路径下

2、进行绑定服务端Service的操作

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private var mRemoteService: ICommandServer? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<TextView>(R.id.bind_service).setOnClickListener(this)
        bindService()
    }

    private val connection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            Log.d("MainActivity","onServiceConnected")
            mRemoteService = ICommandServer.Stub.asInterface(service)
            //绑定服务成功回调
        }

        override fun onServiceDisconnected(name: ComponentName) {
            //服务断开时回调
        }
    }

    private fun bindService() {
        val intent = Intent()
        //Android 5.0开始,启动服务必须使用显示的,不能用隐式的
        intent.action = "com.zhewen.aidlserverstudy.aidl.commandservice"
        intent.`package` = "com.zhewen.aidlserverstudy"
        intent.component = ComponentName("com.zhewen.aidlserverstudy", "com.zhewen.aidlserverstudy.aidl.CommandService")
        val result = bindService(intent, connection, BIND_AUTO_CREATE)
        Log.d("MainActivity", "bindService$result")
    }

    override fun onClick(p0: View?) {
        when(p0?.id) {
            R.id.bind_service -> {
                bindService()
            }
            R.id.add_view -> {
                val result = mRemoteService?.add(11,14)
                findViewById<TextView>(R.id.add_view_result_show).text = "结果——>$result"
            }
        }
    }
}

定向Tag

在基本语法部分简单说明了定向tag的作用,这里进行详细的演示。

实现步骤与前述介绍的基本使用步骤一致

1、定义数据类接

@Parcelize
data class User(
    var age: Int = 0,
    var name: String = ""
):Parcelable {
    fun readFromParcel(parcel: Parcel) {
        this.name = parcel.readString().toString()
        this.age = parcel.readInt()
    }
}

2、定义aidl接口并编译实现

interface ICommandServer {
    void addUserIn(in User user);
    void addUserOut(out User user);
    void addUserInOut(inout User user);
}
private val mRemoteBinder:ICommandServer.Stub = object: ICommandServer.Stub() {

    override fun addUserIn(user: User?) {
        Log.d(TAG,"addUserIn,userName = ${user?.name}")
        user?.name = "addUserIn 修改"
    }

    override fun addUserOut(user: User?) {
        Log.d(TAG,"addUserOut,userName = ${user?.name}")
        user?.name = "addUserOut 修改"
    }

    override fun addUserInOut(user: User?) {
        Log.d(TAG,"addUserInOut,userName = ${user?.name}")
        user?.name = "addUserInOut 修改"
    }
}

3、客户端侧获取服务端Binder对象并调用相关方法

fun addUserIn(){
    val user = User(11,"ClientAddUserIn")
    Log.d(TAG,"Client 调用 addUserIn 之前 User.name = ${user.name}")
    mRemoteService?.addUserIn(user)
    Log.d(TAG,"Client 调用 addUserIn 之后 User.name = ${user.name}")
}

fun addUserOut(){
    val user = User(22,"ClientAddUserOut")
    Log.d(TAG,"Client 调用 addUserOut 之前 User.name = ${user.name}")
    mRemoteService?.addUserOut(user)
    Log.d(TAG,"Client 调用 addUserOut 之后 User.name = ${user.name}")
}

fun addUserInOut(){
    val user = User(33,"ClientAddUserInOut")
    Log.d(TAG,"Client 调用 addUserInOut 之前 User.name = ${user.name}")
    mRemoteService?.addUserInOut(user)
    Log.d(TAG,"Client 调用 addUserInOut 之后 User.name = ${user.name}")
}

看一下调用后的日志输出:

//in 方式,服务端对数据进行修改,但是客户端侧感知不到
CommandClient: Client 调用 addUserIn 之前 User.name = ClientAddUserIn
CommandService: addUserIn,userName = ClientAddUserIn
CommandClient: Client 调用 addUserIn 之后 User.name = ClientAddUserIn
//out 方式,客户端可以感知到服务端的修改,但是无法向服务端传数据,所以服务端未拿到客户端数据
CommandClient: Client 调用 addUserOut 之前 User.name = ClientAddUserOut
CommandService: addUserOut,userName = 
CommandClient: Client 调用 addUserOut 之后 User.name = 
//inout方式
CommandClient: Client 调用 addUserInOut 之前 User.name = ClientAddUserInOut
CommandService: addUserInOut,userName = ClientAddUserInOut
CommandClient: Client 调用 addUserInOut 之后 User.name = null

注意:关于inout方式,个人验证时发现服务端可以拿到客户端数据,服务端修改数据客户端也能够感知到,但是拿不到修改数据。看相关文章都可以拿到数据,具体原因待进一步分析

3.3 进阶用法

3.3.1 权限验证

如果处于安全考虑需要对使用自己服务的客户端进行验证,则可以进行权限验证操作。

方式一

在服务端Manifest文件中自定义一个权限并申请该权限,并在组件Service上指定permission属性权限

<permission android:name="com.zhewen.aidlService.permission.ACCESS_SERVICE"
            android:protectionLevel="normal"/>
<uses-permission android:name="com.zhewen.aidlService.permission.ACCESS_SERVICE"/>

<!--    //......-->

<service android:name=".aidl.CommandService"
         android:permission="com.zhewen.aidlService.permission.ACCESS_SERVICE"
         android:exported="true">
    <intent-filter>
        <action android:name="com.zhewen.aidlserverstudy.aidl.commandservice"/>
    </intent-filter>
</service>

然后在客户端申请这个权限。如果客户端没有申请,则客户端将报错,无法正常启动或绑定该服务。

方式二

在方式一的基础上,进行进一步验证。

在服务端Service类的onBind()方法中进行权限验证

override fun onBind(p0: Intent?): IBinder? {
    //        权限验证
    Log.d(TAG,"onBind")
    if(Binder.getCallingPid() == Process.myPid()) {
        return mRemoteBinder
    }
    val check = checkCallingOrSelfPermission("com.zhewen.aidlService.permission.ACCESS_SERVICE")
    if (check == PackageManager.PERMISSION_DENIED) {
        Log.d(TAG,"onBind,null")
        return null
    }
    Log.d(TAG,"onBind 权限验证通过")
    return mRemoteBinder
}

方式三

方式三是在方式二的基础上进行了包名验证。每一次IPC通信都会调用onTransact(),可以在该方法中进行客户端的包名验证

private val mRemoteBinder:ICommandServer.Stub = object: ICommandServer.Stub() {
    override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
        val clientPkgName = getCallingClientPkgName(getCallingUid())
        Log.d(TAG,"calling pkgName is $clientPkgName")
        return super.onTransact(code, data, reply, flags)
    }
    //......
}
//......

private fun getCallingClientPkgName(callingUid:Int) : String {
    var packageName = ""
    val packages = packageManager.getPackagesForUid(callingUid)
    if (packages != null && packages.isNotEmpty()) {
        packageName = packages[0]
    }
    return packageName
}

3.3.2 死亡代理

Binder运行在服务端进程,若服务端进程因为某些原因死亡,Binder也随之死亡,此时远程调用将会失败,继而影响到客户端功能。

Binder提供了linkToDeath方法可在客户端设置死亡代理,当服务端的Binder对象“死亡”,客户端可以收到死亡通知,此时我们可以重新恢复链接。

class CommandClient private constructor() {

    private var mRemoteService: ICommandServer? = null
    private var mContext:WeakReference<Context>? = null

    fun init(context:Context) {
        mContext = WeakReference(context)
    }

    private val mDeathRecipient:IBinder.DeathRecipient = object : IBinder.DeathRecipient {
        override fun binderDied() {
            Log.d(TAG,"binder died")
            mRemoteService?.asBinder()?.unlinkToDeath(this,0)
            mRemoteService = null
            connectService()
        }
    }

    private val mServiceConnection:ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(componentName: ComponentName?, IBinder: IBinder?) {
            Log.d(TAG,"onServiceConnected")
            try {
                //设置死亡代理
                IBinder?.linkToDeath(mDeathRecipient,0)
                mRemoteService = ICommandServer.Stub.asInterface(IBinder)
            } catch (e : RemoteException) {
                e.printStackTrace()
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName?) {
            Log.d(TAG,"onServiceDisconnected")
            mRemoteService = null
        }
    }

    fun add(value1:Int, value2:Int):Int {
        return mRemoteService?.add(value1,value2)?:-1
    }

    fun connectService() : Boolean? {
        val intent = Intent()
        intent.action = "com.zhewen.aidlserverstudy.aidl.commandservice"
        intent.`package` = "com.zhewen.aidlserverstudy"
        intent.component = ComponentName("com.zhewen.aidlserverstudy", "com.zhewen.aidlserverstudy.aidl.CommandService")
        return mContext?.get()?.bindService(intent,mServiceConnection, BIND_AUTO_CREATE)
    }


    companion object {
        val sInstance = SingletonHolder.holder
        const val TAG = "CommandClient"
    }

    private object SingletonHolder{
        val holder = CommandClient()
    }
}

3.3.3 oneway

为了防止调用线程被阻塞,可以在aidl接口方法添加oneway关键字,则该方法为异步调用。当客户端调用服务端方法时不需要知道返回结果,则可以使用异步调用提高执行效率。

注意

关于oneway的使用在某些场景下可能存在问题。

Binder驱动对oneway的调用类似handler sendmessage,如果服务端的oneway接口处理太慢而客户端调用太多,来不及处理的调用会占满binder驱动缓存,导致其他调用抛出 transaction failed异常。

所以,oneway不适用于客户端高频调用且服务端处理耗时的场景。否则可能出现上述异常,导致业务逻辑缺失。

3.3.4 双向通信

双向通信即服务端也可以主动向客户端返回数据,其核心就是客户端也要创建一个Binder对象,并将其交付给服务端。

1、接口定义

//用于服务端往客户端回传数据
interface IClientCallback{
    void doClientCallback(String value);
}
interface ICommandServer {
    void registerClientCallback(IClientCallback client,String pkgName);
    void unregisterClientCallback(IClientCallback client,String pkgName);
}

上述接口主要用于客户端注册和解注册回调接口,使得服务端可以获取到客户端的Binder对象。

2、在服务端实现注册/反注册

在服务端实现上述定义的注册/反注册方法。Android提供了RemoteCallbackList,专门用于存放监听接口的集合。其内部将数据存储于一个ArrayMap中,key为我们传输的Bindervalue是监听接口的封装。

RemoteCallbackList内部操作数据时已做了线程同步操作。

private val mRemoteBinder:ICommandServer.Stub = object: ICommandServer.Stub() {
    override fun registerClientCallback(client: IClientCallback?) {
        client?.let {
            CommandServer.sInstance.registerClientCallback(it)
        }
    }

    override fun unregisterClientCallback(client: IClientCallback?) {
        client?.let {
            CommandServer.sInstance.unRegisterClientCallback(it)
        }
    }
}
class CommandServer private constructor(){

    companion object {
        val sInstance = SingletonHolder.holder
    }

    private object SingletonHolder{
        val holder = CommandServer()
    }

    //如果客户端异常死亡了,这个对应的回调会自己消失,可在这里进行死亡监听
    private val mClientCallbackList = object :RemoteCallbackList<IClientCallback>(){

        override fun onCallbackDied(callback: IClientCallback?, cookie: Any?) {
            super.onCallbackDied(callback, cookie)
        }
    }

    fun registerClientCallback(client: IClientCallback) {
        mClientCallbackList.register(client)
    }

    fun unRegisterClientCallback(client: IClientCallback) {
        mClientCallbackList.unregister(client);
    }
}

3、客户端实现暴露的接口和注册与反注册

private val mClientCallback: IClientCallback.Stub = object : IClientCallback.Stub() {
    override fun doClientCallback(value: String?) {
        Log.d(TAG,"doClientCallback,value = $value")
    }
}

private val mServiceConnection:ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(componentName: ComponentName?, IBinder: IBinder?) {
        Log.d(TAG,"onServiceConnected")
        try {
            //设置死亡代理
            IBinder?.linkToDeath(mDeathRecipient,0)
            mRemoteService = ICommandServer.Stub.asInterface(IBinder)
            mRemoteService?.registerClientCallback(mClientCallback);
        } catch (e : RemoteException) {
            e.printStackTrace()
        }
    }

    override fun onServiceDisconnected(componentName: ComponentName?) {
        Log.d(TAG,"onServiceDisconnected")
        mRemoteService?.unregisterClientCallback(mClientCallback)
        mRemoteService = null
    }
}

4、在服务端模拟回调

//CommandService.kt
//死循环 每隔5秒添加一次person,通知观察者
private val serviceWorker = Runnable {
    while (!Thread.currentThread().isInterrupted) {
        Thread.sleep(20000)
        val user = User(Random().nextInt(100),"server")
        Log.d(TAG, "服务端 onDataChange() 生产的 user = $user}")
        CommandServer.sInstance.onDataChange(user)
    }
}

override fun onDestroy() {
    CommandServer.sInstance.clean()
    super.onDestroy()
}
fun onDataChange(user: User) {
    val callbackCount = mClientCallbackList.beginBroadcast()
    for (i in 0 until callbackCount) {
        try {
            mClientCallbackList.getBroadcastItem(i)?.doClientCallback(user.age.toString())
        } catch (e:RemoteException) {
            e.printStackTrace()
        }
    }
    mClientCallbackList.finishBroadcast()
}

fun clean() {
    mClientCallbackList.kill()
}