Android binder通信机制分析

1,000 阅读3分钟

学习binder,我建议先了解binder在哪里应用,然后你才知能想去更了解它。。

未命名绘图.drawio.png

1.哪里使用

在android中,应用间通信、Intent、四大组件的交互都使用了binder来传递消息,可以说android中的ipc(进程间通信都用到了binder机制)。

2.为什么

我们可以想想,假如没有bidner,我们用什么来实现进程间的通信。 我们知道android是基于linux实现的。那么linux上通信方式有什么呢?

  • 无名管道(pipe)
  • 命名管道(fifo)
  • 内存映射(mapped memeory),
  • 消息队列(message queue)
  • 共享内存(shared memory)
  • 信号量(semaphore)
  • 信号(signal)
  • 文件(file)
  • 套接字(Socket)

我们可以看到linux本身就有非常多能用来通信的机制了,为什么聪明的google android工程师大佬们要做重复造轮子的事情。我们来看看一些和linux通信机制的简单对比。

与linux通信机制对比.drawio.png

可以看到Binder相比于linux传统的ipc机制,有着效率、架构、安全的全面优势,所以google工程师考虑了很多,这也是google工程师为什么要在android上重新来构建一套android ipc通信机制。

3.怎么用

在项目里使用aidl来完成app间通信。一个简单的demo。之后补一个视频吧,这里就不详细描述了。 放一个b站的链接,有兴趣可以可以看下。

【高版本Android使用AIDL的方法-哔哩哔哩】 b23.tv/eVLpzaz image.png 这样我们在客户端就能接收到服务端返回的数据。

我们看看代码是怎么走的

//通过asInterface传入service对象拿到了接口实例,调用了stringFromSercice
public static com.gino.binderserver.IMyAidlInterface asInterface(android.os.IBinder obj)
{
  //返回的Proxy
  return new com.gino.binderserver.IMyAidlInterface.Stub.Proxy(obj);
}

//实际上调用了Proxy的getStringFromSercice方法
@Override public java.lang.String getStringFromSercice() throws android.os.RemoteException{
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.lang.String _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
              //transact 在这里请求了服务端的transact方法传入参数
          boolean _status = mRemote.transact(Stub.TRANSACTION_getStringFromSercice, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getStringFromSercice();
          }
      }
    }
    
//服务端的onTransact方法
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
      //调用方法将值返回
      java.lang.String _result = this.getStringFromSercice();

  }
}

aidl方法调用.png

看下面的图。

aidl通信图形.drawio.png

借用一张分层架构的图

image.png

原理整理分析

再借用一张经典的图

image.png

Binder怎么进行的一次拷贝?

Binder IPC 正是基于内存映射(mmap)来实现的,一次完整的 Binder IPC 通信过程通常是这样:

1、Binder 驱动在内核空间创建一个数据接收缓存区;

2、在内核空间开辟一块内核缓存区,

  • 建立内核缓存区和内核中数据接收缓存区之间的映射关系;
  • 内核中数据接收缓存区和接收进程用户空间地址的映射关系;

3、发送数据完成了一次进程间的通信。

  • 发送方进程通过系统调用 copy_from_user() 将数据 拷贝 到内核中的内核缓存区;
  • 由于内核缓存区和数据接收缓存区存在内存映射,因此也就相当于把数据发送到了数据接收缓存区;
  • 由于数据接收缓存区和进程的用户空间存在内存映射因此也就相当于把数据发送到了接收进程的用户空间。

内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

到这里我们差不多就知道原理了。想要去体会细节可以去看看native和驱动的底层实现。 建议c++足够熟练再去看驱动的代码。

Binder进程通信底层原理

深入分析Android Binder 驱动