AIDI 和 Binder

一、概述

Binder 可以提供系统中任何程序都可以访问的全局服务。Android Binder 框架分为服务器接口、Binder 驱动以及客户端接口

  1. 服务端接口:实际上是 Binder 类的对象,该对象一旦创建,内部则会启动一个隐藏线程,会接收 Binder 驱动发送的消息,收到消息后,会执行 Binder 对象中的onTransact()函数,并按照该函数的参数执行不同的服务端代码;
  2. Binder 驱动:该对象也为 Binder 类的实例,客户端通过该对象访问远程服务。
  3. 客户端接口:获得 Binder 驱动,调用其transact()发送消息至服务器

二、AIDL 的使用

创建 aidl 文件;创建一个 Service;利用 XxxAIDL.Stub 通过onBind()返回 Binder 给客户端,暴露接口;客户端通过 ServiceConnection 与之交互。

服务端

创建一个包名:com.demo.aidl,在包内创建一个 ICalcAIDL.aidl 文件:

1
2
3
4
5
package com.demo.aidl.calc
interface ICalcAIDL{
int add(int x, int y);
int min(int x, int y);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.demo.hg_binder;

import com.demo.aidl.ICalcAIDL;
...
public class CalcService extends Service{
private static final String TAG = "server";

public void onCreate(){
...
}
public IBinder onBind(Intent t){
...
return mBinder;
}
public void onDestroy(){
...
super.onDestroy();
}
public boolean onUnbind(Intent intent){
return super.onUnbind(intent);
}
public void onRebind(Intent intent){
super.onRebind(intent);
}
private final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub(){
@Override
public int add(int x, int y) throws RemoteException{
return x + y;
}

@Override
public int min(int x, int y) throws RemoteException{
return x - y;
}
};
}

再在 AndroidManifest 中注册

1
2
3
4
5
6
<service android:name="com.demo.hg_binder.CalcService">
<intent-filter>
<action android:name="com.demo.aidl.calc"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class MainActivity extends Activity{
private ICalcAIDL mCalcAidl;

private ServiceConnection mServiceConn = new ServiceConnection(){
@Override
public void onServiceDisconnected(ComponentName name){
mCalcAidl = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service){
mCalcAidl = ICalcAIDL.Stub.asInterface(service);
}
}

@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

public void bindService(){
Intent intent = new Intent();
intent.setAction("com.demo.aidl.calc");
bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
}

public void unbindService(){
unbindService(mServiceConn);
}

public void addInvoked() throws Exception{
if(mCalcAidl != null){
int addRes = mCalcAidl.add(12, 12);
...
}else{
// 服务器被异常杀死,请重新绑定服务端
}
}

public void minInvoked() throws Exception{
if(mCalcAidl != null){
int minRes = mCalcAidl.min(58, 12);
...
}else{
// 服务器被异常杀死,请重新绑定服务端
}
}
}

分析 AIDL 生成的代码

Stub 类 onTransact()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static abstract class Stub extends android.os.Binder implement com.demo.aldl.calc{
...
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{
switch(code){
case INTERFACE_TRANSACTION:{
reply.writeString(DESCRIPTOR);
return true;
}
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_min:{
...
}
return super.onTransact(code, data, reply, flags);
}
}
}

code 是一个整型的唯一标识,用于区分执行哪个方法,客户端会传递此参数,告诉服务端执行哪个方法;data 是客户端传递过来的参数;reply 是服务端返回给客户端的值;flags 标明是否有返回值,0 为有(双向),1 为没有(单向)

Binder 原理剖析

为什么必须理解 Binder ?

  • 为什么 Activity 间传递对象需要序列化?
  • Activity 的启动流程是什么样的?
  • 四大组件底层的通信机制是怎样的?
  • AIDL 内部的实现原理是什么?
  • 插件化编程技术应该从何学起?……

为什么是 Binder

性能、稳定性和安全性

Binder 优势

Linux 下传统的进程间通信原理

  • 进程隔离:内存是不共享的
  • 进程空间划分:用户空间(User Space)、内核空间(Kernel Space)。它们之间是隔离的
  • 系统调用:用户态与内核态

通常的做法是将消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用copyfromuser()将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用copytouser()将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间的通信。

传统 IPC 通信原理

存在两个问题:

  1. 性能低下
  2. 接收数据的缓存区大小无法知道,只能尽可能大,浪费空间或是时间。

Binder 跨进程通信原理

动态内核可加载模块 && 内存映射

跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分。Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核加载模块(Loadable Kernel Module, LKM)的机制。模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。

在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Driver)

如何通过这个内核模块(Binder Driver)来实现通信的呢?—— 内存映射。

内存机制简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反映到内核空间,反之内核空间对这段区域的修改也能直接反映到用户空间。

内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动,各自空间的修改能被对方及时感知。

Binder IPC 机制中涉及到的内存映射通过mmap()来实现。

Binder IPC 实现原理

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

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

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

Binder IPC 通信过程

Binder 通信模型

Client/Server/ServiceManager/驱动

关系图

Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中的服务器(Server)、客户端(Client)、DNS 域名服务器(ServiceManager)以及路由器(Binder 驱动)之间的关系。

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

ServiceManager 与实名 Binder。ServiceManager 和 DNS 类似作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用。注册了名字的 Binder 叫 实名 Binder,就像网站一样除了有 IP 地址还有自己的域名。

Server 创建了 Binder,并为他起一个字符形式,可读易记的名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager,通知 ServiceManager 注册一个名为所命名的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将这个以及新建的引用打包传给 ServiceManager,ServiceManager 收到数据后从中取出名字和引用填入查找表。

ServiceManager 是一个进程,Server 是另外一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信?!ServiceManager 和其他进程同样采用 Binder 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册、查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDER_SET_CONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其他手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client

Server 向 ServiceManager 中注册了 Binder 以后,Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder,ServiceManager 收到请求后从请求数据包中取出 Binder 名称,在查找表中找到对应的条目,取出对应的 Binder 引用作为回复发送回发起请求的 Client。

Binder 通信过程

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

Binder 通信中的代理模式

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

当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。


参考引用

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

Android aidl Binder框架浅析