Binder 的学习过程

171 阅读7分钟

写给 Android 应用工程师的 Binder 原理剖析

Android进程间通信(IPC)机制Binder简要介绍和学习计划

Android跨进程通信:图文详解 Binder机制 原理

对于还没太了解Binder 的同学来说,通过上面几篇文章学习Binder 是非常不错的选择,由浅入深的讲解了Binder 的构成及原理。以下是个人的笔记及理解。

Binder 是什么

从Android 的角度来说,Binder 就是实现了IBinder 的一个类,它具有跨进程通信的能力。

为什么选择Binder

  1. 性能仅次于文件共享,只需要一次数据拷贝,文件共享操控复杂,难以使用。
  2. 稳定性使用C/S架构,责任明确,结构清晰
  3. 安全性,添加可靠UID/PID身份,既支持实名Binder也支持匿名Binder。

Binder 框架的构成

Binder框架定义了四个角色:Server,Client,ServiceManager 以及Binder 驱动。其中Server,Client,ServiceManager 运行于用户空间,Binder驱动 运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),驱动是路由器。

  • Binder Driver(Binder 驱动)

    Binder 驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

  • ServiceManager、Service

    ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder,就像网站一样除了除了有 IP 地址意外还有自己的网址。Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。

    细心的读者可能会发现,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。

  • Client

    Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用一样。

Binder 通信过程

  1. 注册服务器

    Server 通过 Binder驱动 向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。

  2. 获取服务

    Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用。

  3. 使用服务

    Client 通过获取到的Binder 实体的引用就能实现和 Server 进程的通信。

AIDL

AIDL 是Binder 的一种实现,我们可以通过它进行了解Binder 的使用。

  • 各 Java 类职责描述

    • 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 文件生成的代码,删除了些业务代码,我们只需要关注 asInterface、onTransact 两个方法。

    /*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.fyt.aidldemo;
public interface IBookManager extends android.os.IInterface
{
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.fyt.aidldemo.IBookManager
  {
    private static final java.lang.String DESCRIPTOR = "com.fyt.aidldemo.IBookManager";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.fyt.aidldemo.IBookManager interface,
     * generating a proxy if needed.
     */
    public static com.fyt.aidldemo.IBookManager asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.fyt.aidldemo.IBookManager))) {
        return ((com.fyt.aidldemo.IBookManager)iin);
      }
      return new com.fyt.aidldemo.IBookManager.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;
        }
        case TRANSACTION_getBookList:
        {
          data.enforceInterface(descriptor);
          java.util.List<com.fyt.aidldemo.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        case TRANSACTION_addBook:
        {
          data.enforceInterface(descriptor);
          com.fyt.aidldemo.Book _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.fyt.aidldemo.Book.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          this.addBook(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_registerListener:
        {
          data.enforceInterface(descriptor);
          com.fyt.aidldemo.IOnNewBookArrivedListener _arg0;
          _arg0 = com.fyt.aidldemo.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
          this.registerListener(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_unregisterListener:
        {
          data.enforceInterface(descriptor);
          com.fyt.aidldemo.IOnNewBookArrivedListener _arg0;
          _arg0 = com.fyt.aidldemo.IOnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
          this.unregisterListener(_arg0);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.fyt.aidldemo.IBookManager
    {
      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;
      }
      public static com.fyt.aidldemo.IBookManager sDefaultImpl;
    }
    
  }
}
  • asInterface(android.os.IBinder obj)

    用于将服务端的Binder对象 转换成客户端需要的AIDL 接口类型的对象,这个转换过程是区分进程的,如果客户端和服务端在同一个进程返回的是Stub对象本身,否则返回的是系统封装后的Stub.proxy 对象(代理对象)。

  • onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

    这个方法运行在服务端中的Binder 线程池中,当客户端跨进程请求时,远程请求会通过系统底层封装后交由此方法处理。服务端通过参数 code 可以确定客户端的请求目标方法,接着从 data 中获取对应的参数,再将结果写入到 reply 中。需要注意的是,如果该方法返回false,那么客户端的请求会失败,因此可以利用这个特性做权限验证。

具体代码可以看我写的一个demo