Android Binder机制的感性认识

319 阅读10分钟

研究了一段时间系统底层,对于Binder有点点粗浅的入门心得,没有那个水平讲的细致入微,全方面分析。

如果想学习源码细节:

Binder学习指南 | Weishu's Notes (30条消息) Android Bander设计与实现 - 设计篇_universus的博客-CSDN博客_android binder

(30条消息) Android Binder框架实现源码深入分析_IT先森的博客-CSDN博客

Category - Gityuan博客 | 袁辉辉的技术博客

Android Binder - 魅族内核团队 (meizu.com)

对于进程间通信 就可以对象间通信取理解。 只不过是对象通信可以直接调用彼此的方法,进程间通信需要通过中间件中转一下,这个中间件在Android中叫做Binder。

实质上通过Binder也是 A进程调用B进程的方法呗。 如果想理清源码的某一段逻辑,需要理解aidl套路,知道生成文件的作用,系统服务也按照aidl的套路来的。理解了各个文件的作用,那么跟踪业务方法就好了。

Binder的目的

做什么事情都要有一个目的,写程序也一样,为了达成某个目标而写一段程序。

每个APP都是一个进程,一台手机有N多个进程。用户日常使用的淘宝微信各种各样的APP,支撑手机运行各种系统服务都是进程。

一个手机就好像一个独立的有机世界,每个进程不是孤岛,它们之间也会发生数据交换。为了安全,操作系统有进程隔离机制,进程之间不能随意访问数据。

但是进程之间又有数据交换的需求,于是催生出进程间数据交换的方式。

Binder目的是为了进程间通信。进程间通信IPCInter-Process Communication

通信的概念与流程

我觉得首先要聊聊通信。不论是现实世界还是编程世界通讯都是一个常见的场景。

现实世界中人们的通信方式经历了很多次变化:扯嗓子喊—写信—打电话—电子邮件—微信聊天

通信方式从原始到先进,发起通信这个过程却总是类似的。日常开发中客户端与服务器通信的前提是客户端必须知道服务器的接口地址,才能够建立通信,数据交换,通信完成。

用打电话举例:

  1. 在发现地址的过程中,可能会出现:
    1. 明确知道电话号。
    2. 不知道电话号,托人打听最终知道了电话号。
    3. 某单位想要别人方便联系自己,主动暴露电话号
  2. 知道电话号后,开始拨号。
    1. 拨号过程存在校验,空号,欠费,无信号之类的。
  3. 拨号成功建立连接。可能有个啥机器,接收语音,翻译解析成电流数据。最终实现通信

两端通信基本流程:寻找地址,建立通信,数据的解析传递交互,通信完成。可以说无论是现实还是虚拟,所有的通信都基于上述流程。

只不过各自的名称不同 如:信件通信,电话通信,系统进程间通信

Binder C/S架构

如图所示:

Untitled.png

Binder框架是CS架构核心角色有三个:Server,Client,Binder驱动

Server 和 Client 的角色定位并不是一成不变的。哪一个进程发起请求就是Client。

场景一,进程A 调用 进程B的方法,进程B提供服务,实现逻辑。 那么进程A就是Client,进程B就是Server

场景二,反过来,进程B调用进程A。那么进程B是Client,进程A是Client

因为进程隔离的存在,进程A进程B在调用时只是看起来直接调用了,实际上它们之间的通信是经过Binder驱动处理的。

ServiceManager

ServiceManager单独拎出来说一下。很多文章把ServiceManager归到通信模型中,虽然ServiceManager在Binder中有举足轻重的位置,放到通信模型中一起讲会有点混乱。

ServiceManager 是一个进程,系统启动时创建,用于管理系统服务

AMS,WMS,PMS等等所有的系统服务,在初始化时创建自身的Binder对象,通过ServiceManager 与 Binder驱动通信,通知Binder驱动创建当前服务的Binder实体,保存相关信息。

同时ServiceManager会保留一份Binder实体的引用,以key-value形式存储。

在开发中通过context.getSystemService(name) 获取系统进程的Binder引用,就是App进程与ServiceManager 进行进程间通信

模拟一个情景,App进程调用AMS的方法。

  1. 乍一看 好像是只进行了一次进程间通信 但实际上是两次进程间通信
  2. 第一次 调用 getSystemService(name) App进程 与 ServiceManager进程通信,获取AMS的binder引用。
  3. 第二次 App进程 与 AMS通信 获取数据
  4. 根据Binder通信模型,第一次App进程是Client,ServiceManager是Server。 第二次App进程是Client,AMS是Server

所以我感觉 ServiceManager 不该放到Binder通信模型中 或是 整体结构中讲,虽然它是一个很重要的一部分。

ActivityManager activityManager = (ActivityManager) getSystemService(Service.ACTIVITY_SERVICE);
List<ActivityManager.AppTask> list = activityManager.getAppTasks();

实名Binder与匿名Binder

系统服务的Binder引用是通过name获取的,因为有名字所以注册在ServiceManager统一管理的系统服务的Binder又叫做实名Binder。

也很好理解,每个系统服务负责单独的一块功能,都是极其重要,比如:电量,窗口,四大组件,包管理等等。缺少了它们Android系统根本无法正常功能,所以设计出ServiceManager,管理所有系统服务的Binder引用。可以随意获取,完成通信。

但并不是所有的Binder都需要注册到ServiceManager。开发了一个App,对外暴露接口获取App的一条业务数据,除了提前沟通好的业务方,其他进程没有理由与你发生进程间通信,这种没有注册到ServiceManager的Binder叫做匿名Binder。

匿名Binder 是 应用进程 通过bindService()实现的。

以开发者的角度说

应用进程与系统进程通信,使用context.getSystemService(name) 获取系统服务的Binder引用,是实名Binder。

应用进程和另一个应用进程通信,使用bindService() 其实就是aidl机制 。因为没有在ServiceManager注册,所以叫做匿名Binder。

Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求 引用自:(30条消息) Android Bander设计与实现 - 设计篇_universus的博客-CSDN博客_android binder

Binder驱动与Binder通信协议

在开发中使用进程间通信,我们只是看起来获取了另一个进程的对象,调用其公开的方法。但实质上每一个进程都是在与Binder驱动打交道。 大概可以理解为:进程A发送数据到Binder驱动,Binder驱动发送给进程B。

在讲述Binder驱动做了什么事之前,先看看另一个更为常见的进程间通信的例子。客户端发起http请求调用服务端接口。

网络进程通信:客户端—Http—服务器

Binder进程通信:客户端—Binder—服务器

同样都是进程间通信,只不过一种技术手段是Http,另一种技术手段是Binder。不同的方式达到同样的目的。使得进程A能够调用进程B的方法

HTTP协议大家肯定更了解,就用Http的视角去讲述,借此理解Android中的Binder驱动。

计算机虽然很强大,但是它不智能,想要计算机理解一些信息必须是基于一定规则之下。比如常见的Json,xml。在传输过程中比如严格按照规范,计算机才能解析出正确的数据。

Http协议正是如此,它是为了让两台计算机能够识别传输的数据完成交流目的而定义的一种规则。发数据的时候必须发送约定好的数据格式,不然就识别不出。Http协议的具体内容就是Http报文,通过报文可以使服务器知道,客户端要调用哪一个方法,传递了什么数据。

同理Binder也定义了一套自己的通信协议

Binder协议基本格式是(命令+数据),使用ioctl(fd, cmd, arg)函数实现交互。命令由参数cmd承载,数据由参数arg承载,随cmd不同而不同

细节请看:

Android Bander设计与实现 - 设计篇(第四段 Binder 协议)

Android底层:通熟易懂的分析binder--2. binder进程通信协议及“记录链路”结构体 - 掘金 (juejin.cn)

AIDL分析

定义AIDL接口

创建aidl后缀的接口,编译后AndroidStudio会自动生成代码,

IUserInterface.aidl

interface IUserInterface {
    //登录
    void login(String account,String password);
}

生成文件分析

  1. 生成IUserInterface接口
    1. 继承自 android.os.IInterface
    2. 生成在aidl文件中声明的抽象方法
  2. 生成抽象类Stub 继承 android.os.Binder 实现 IUserInterface 接口 , server端使用。
    1. 生成方法 asInterface(IBinder), 核心逻辑是 判断参数IBinder 是本地对象还是远程对象。如果Client 和 server 是在同一个进程 则会被判断为本地对象,直接强转类型返回。如果是远程对象 则生成代理类Proxy 返回。
    2. 生成方法 onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),接收Client发送来的数据。
      1. 参数code 函数编号,通过编号来确定 要调用哪一个函数。服务端定义login方法 code值是1,register方法code值是2。 客户端只需要传递函数编号 就可以确定要调用那个服务端方法了
      2. 参数data 客户端传递的参数
      3. 参数reply 服务端返回值
  3. 生成静态内部类Proxy 。Client端使用
    1. 持有远程Binder对象,用来调用服务端接口
    2. 实现IUserInterface ,实现定义的抽象方法,组装参数,在其中调用远程Binder对象的方法,完成进程间通信
  4. 总结
    1. android.os.IInterface 表明服务端具有什么功能
    2. android.os.Binder 表明当前对象是Binder 具有进程间通信功能。(Binder类实现IBinder接口)
    3. 抽象类 Stub 继承 android.os.Binder 实现android.os.IInterface 说明它是真实Binder对象,具有进程间通信能力,对外提供一些功能。
    4. 静态内部类Proxy 只现实android.os.IInterface 接口,持有Binder对象。说明它只提供这些能力,本身不是Binder对象,不具备进程间通信的能力。
    5. 经过上述分析 阅读源码 或者 手写aidl 时应该不会毫无头绪了,套路是固定
public interface IUserInterface extends android.os.IInterface
{
  /** Default implementation for IUserInterface. */
  public static class Default implements com.example.ipc.server.IUserInterface
  {
    //登录

    @Override public void login(java.lang.String account, java.lang.String password) throws android.os.RemoteException
    {
    }
    @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.example.ipc.server.IUserInterface
  {
    private static final java.lang.String DESCRIPTOR = "com.example.ipc.server.IUserInterface";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.example.ipc.server.IUserInterface interface,
     * generating a proxy if needed.
     */
    public static com.example.ipc.server.IUserInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.ipc.server.IUserInterface))) {
        return ((com.example.ipc.server.IUserInterface)iin);
      }
      return new com.example.ipc.server.IUserInterface.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_login:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          java.lang.String _arg1;
          _arg1 = data.readString();
          this.login(_arg0, _arg1);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.example.ipc.server.IUserInterface
    {
      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 login(java.lang.String account, java.lang.String password) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(account);
          _data.writeString(password);
          boolean _status = mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().login(account, password);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.example.ipc.server.IUserInterface sDefaultImpl;
    }
    static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(com.example.ipc.server.IUserInterface 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.example.ipc.server.IUserInterface getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  //登录

  public void login(java.lang.String account, java.lang.String password) throws android.os.RemoteException;
}