Binder

Binder的定义

Binder是Android系统中基于开源OpenBinder实现的一种进程间通信(IPC)的机制。

Android应用程序是由Activity,Service、BroadcastReceiver和Content Provider这四大组件组成,有时这些组件运行在不同的应用或进程中,需要使用Binder机制来进行通信。

Android系统对应用层提供的各种服务,如ActivityManagerService、PackageManagerService等都是基于Binder机制来实现的。

Binder的优势

Android系统是基于Linux内核的,虽然Linux已经提供了管道、消息队列、共享内存和Socket等IPC机制,但基于性能、稳定性和安全性等因素,Android系统还是设计了自己特有的Binder IPC机制。

性能

Socket作为一款通用接口,其传输效率低、开销大,主要用在跨网络进程间通信和本机上进程间的低速通信。

管道和消息队列采用存储-转发方式,即数据从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少经历两次拷贝。

共享内存虽然无需进行内存拷贝,但控制复杂、难以使用。

Binder只需要一次内存拷贝,性能上仅次于共享内存。

稳定性

Binder基于C/S架构,架构清晰、职责明确且相互独立,所以稳定性更好。

安全性

传统的IPC机制没有任何安全措施,完全依赖上层协议来确保。

Android系统为每个安装好的App分配了UID,因此进程的UID是鉴别身份的重要标志。而传统IPC的接收方无法获得对方可靠的进程ID/用户ID(PID/UID),从而无法鉴别对方身份。

传统IPC可以由用户在数据包中填入UID/PID,但这样不可靠,可靠的身份标识只能由IPC机制在内核中添加。

传统IPC的访问接入点是开放的,只要知道这些接入点的程序都可以建立连接,无法阻止恶意程序通过猜测接入点来获得连接。

Binder IPC机制可以解决身份鉴别、访问接入控制等问题。

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

基本概念

Linux跨进程通信

上图展示了Linux跨进程通信涉及到的一些基本概念:

  • 进程隔离

  • 进程空间划分:用户空间(User space)/内核空间(Kernel space)

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

进程隔离

在操作系统中,进程之间的内存是不共享的。进程之间要进行数据交互需要采用特殊的通信机制,称为进程间通信(Inter Process Communication, IPC)。

进程空间划分

现代操作系统都采用虚拟存储器,对于32位系统而言,它的寻址空间为2的32次方,即4GB。

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

为了使用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间和内核空间。

对于32位的Linux系统而言,将最高的1GB给内核使用,即内核空间;而较低的3GB供给各进程使用,即用户空间。

系统调用

内核空间能直接访问内核资源,如文件操作、访问网络等,而用户需要借助系统调用来实现。

系统调用是用户空间访问内核的唯一方式,这保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。

Linux使用两级保护机制:0级供系统内核使用,3级供用户程序使用。

当进程使用系统调用而陷入内核代码中时,其处于内核运行态(内核态)。此时,处理器在特权级最高(0级)的内核代码中执行。

每个进程都有自己的内核栈,当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。

当进程执行用户自己的代码时,其处于用户运行态(用户态)。此时,处理器在特权级最低(3级)的用户代码中执行。

系统调用主要是通过如下两个函数来实现:

  • copy_from_user(),将数据从用户空间拷贝到内核空间。

  • copy_to_user(),将数据从内核空间拷贝到用户空间。

传统IPC通信原理

在传统IPC中,进程之间的通信是:

  • 发送进程将要发送的数据放在自己的用户空间开辟出的**内存缓存区(发送进程)**中,通过系统调用进入内核态。

  • 内核程序在内核空间中开辟出一块内核缓存区,再调用copy_from_user()将数据从发送进程的内存缓存区拷贝到内核缓存区中。

  • 接收进程在接收数据时,在自己的用户空间中开辟出一块内存缓存区(接收进程)。

  • 内核程序调用copy_to_user()将数据从内核缓存区拷贝到接收进程的内存缓存区。

传统IPC通信原理

这种传统的IPC通信方式主要有两个问题:

  • 性能低下,一次数据传递需要经历两次拷贝:发送进程的内存缓存区 -> 内核缓存区 -> 接收进程的内存缓存区。

  • 接收数据的内存缓存区由接收进程提供,但接收进程事先并不知道需要多大的空间来存放数据,所以只能开辟尽可能大的内存空间(浪费空间)或者先调用API查询消息大小(耗时)。

Binder跨进程通信原理

动态内核可加载模块

模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核,作为内核的一部分来运行。

Android系统是基于Linux内核的,而得益于Linux的动态内核可加载模块(Loadable Kernel Module, LKM)的机制,Android系统可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过此内核模块作为桥梁来实现通信。

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

内存映射

内存映射就是将用户空间的一块内存区域映射到内核空间。

映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之,内核空间对这个区域的修改也能直接反应到用户空间。

内存映射能够减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反应到映射的内存区域,从而被对方及时感知。

mmap()是操作系统中一种内存映射的方法,而Binder IPC机制中涉及到的内存映射是通过mmap()来实现的。

Binder IPC实现原理

Binder IPC使用mmap()在内核空间创建数据接收的缓存空间。

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

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

  2. 在内核空间开辟一块内核缓存区,并建立内核缓存区数据接收缓存区之间的映射关系,以及数据接收缓存区接收进程用户空间地址的映射关系。

  3. 发送进程利用系统调用copy_from_user()函数将数据拷贝到内核缓存区,由于内核缓存区、数据接收缓存区和接收进程的用户空间地址三者之间存在内存映射,所以此操作相当于把数据发送到了接收进程的用户空间。

Binder IPC实现原理

Binder通信模型

一次完整的进程之间的通信必然至少包含两个进程,即客户端进程(Client)和服务端进程(Server)。

由于进程隔离机制的存在,通信双方需要借助Binder来实现。

Binder通信组件

Binder是基于C/S架构的,由一系列组件组成,包括Client、Server、ServiceManager、Binder Driver。

Client、Server、ServiceManager运行在用户空间,而Binder Driver运行在内核空间。

ServiceManager和Binder Driver由Android系统提供,而Client和Server由应用程序实现。

Client、Server和ServiceManager都是通过系统调用open、mmap和ioctl来访问设备文件/dev/binder,从而与Binder Driver交互来间接实现跨进程通信。

Binder通信组件

Server、Client、ServiceManager和Binder Driver等Binder通信组件在通信过程中扮演的角色可以依次类比成互联网中的服务器、客户端、DNS服务器以及路由器之间的关系。

类比成网页访问

Binder通信过程

  1. 一个进程使用BINDER_SET_CONTEXT_MGR命令通过Binder Driver将自己注册成为ServiceManager。

  2. Server进程通过Binder Driver向ServiceManager中注册Server Binder实体,表明可以对外提供服务。

  3. Binder Driver为这个Server Binder创建位于内核中的实体节点以及ServiceManager对此实体的引用。ServiceManager将Server名称和刚新建的实体引用填入查找表中。

  4. Client进程在Binder Driver的帮助下,通过服务名从ServiceManager中获取到对Server Binder实体的引用,通过这个引用就能实现和Server进程的通信。

Binder通信过程

Binder通信中的代理模式

Client进程无法直接使用Server进程中的某个对象(Object)。

当Client进程请求使用Server进程的Object时,Binder Driver会将Server的Object转换成一个代理对象ProxyObject,然后返回给Client。

Binder通信中的代理模式

这个ProxyObject具有和Server Object一样的方法,但却没有Server Object真正的能力。因此,Client调用请求是由Binder Driver转交给Server来完成:

  • 当Client调用ProxyObject的方法时,Binder Driver会通知Server进程,进而调用对应的Server Object的指定方法来处理请求。

  • Binder Driver拿到Server的返回结果后转发给Client,通信完成。

Binder跨进程通信的编码实现

各Java类职责

  • IBinder:IBinder是一个接口,代表了一种跨进程通信的能力。只要实现了这个接口,这个对象就能跨进程传输。

  • IInterface:IInterface代表的是Server进程对象具备怎样的能力,比如提供哪些方法。其实IInterface对应的就是AIDL文件中定义的接口。

  • Binder:android.os.Binder类,表示Binder本地对象。

  • Stub:编译工具根据AIDL文件生成的类。这个类继承Binder,表明它是一个Binder本地对象;它实现了IInterface接口,表明它具有Server承诺给Client的能力。Stub是一个抽象类,具体的IInterface接口实现需要由开发者实现。

  • Proxy:编译工具根据AIDL文件生成的Stub类中的内部类。它实现IInterface接口,表明它具有Server承诺给Client的能力。它是一个Server进程的Binder对象的本地代理。

Binder通信的自动编码实现(AIDL)

在Android应用开发中,实现进程间通信用的最多的是AIDL。

首先,定义AIDL文件,表明Server具备怎样的能力。如IRemoteService.aidl

/* IRemoteService.aidl */
package com.daking.aidl;

interface IRemoteService {
    String getName();
}
1
2
3
4
5
6

接着,编译后会自动生成对应的java文件。比如,编译器根据IRemoteService.aidl生成IRemoteService.java,这个java文件包含一个IRemoteService接口、一个Stub静态抽象类和一个Proxy静态类。而Proxy是Stub的静态内部类,而Stub又是IRemoteService的静态内部类。

Android对AIDL自动生成的代码文件使用这种多层嵌套的设计的原因是,当有多个AIDL文件时,将IRemoteService、Stub和Proxy放在同一个文件中能有效避免Stub和Proxy重名的问题。

/* IRemoteService.java */
public interface IRemoteService extends android.os.IInterface {
    
    public static abstract class Stub extends android.os.Binder implements com.daking.aidl.IRemoteService {
        private static final java.lang.String DESCRIPTOR = "com.daking.aidl.IRemoteService";
        
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static com.daking.aidl.IRemoteService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.daking.aidl.IRemoteService))) {
                // 若Client和Server位于同一进程,直接返回Stub对象
                return ((com.daking.aidl.IRemoteService) iin);
            }
            // 若Client和Server在不同进程,返回Stub.Proxy对象
            return new com.daking.aidl.IRemoteService.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        // 处理由Binder Driver转发的Client请求
        @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_getName: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getName();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.daking.aidl.IRemoteService {
            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 java.lang.String getName() throws android.os.RemoteException {
                // 将Client的调用请求转发给Binder Driver,进而调用Server。
                // 接收Binder Driver返回的Server处理结果。
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
        
        // 对外提供的各方法ID
        static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }
    
    public java.lang.String getName() throws android.os.RemoteException;
}
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

Server进程需要构建一个android.app.Service子类,为Client进程提供服务,并在这个Service子类中实现IRemoteService.Stub

/* RemoteService */
public class RemoteService extends Service {
    public RemoteService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        @Override
        public String getName() throws RemoteException {
            return "RemoteService from server process";
        }
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Client进程绑定RemoteService服务,随后通过IRemoteService.Stub.asInterface()将服务绑定成功回调的IBinder对象转换成IRemoteService对象。

/* Client */
IRemoteService mService;

public void onServiceConnected(ComponentName name, IBinder service) {
    mService = IRemoteService.Stub.asInterface(service);
}
1
2
3
4
5
6

这个IRemoteService对象是Stub对象或Proxy对象,这取决于Client和Server是否在同一个进程中。使用该对象可调用Server提供的功能。

Binder通信的手动编码实现

根据AIDL自动编码实现的Binder通信来理解原理,从而使用手动编码实现高可读性、结构更清晰的Binder通信。

首先,定义一个IRemoteService接口,继承IInterface,表明Server端具备怎样的能力。另外,也可以定义一些通用常量。

public interface IRemoteService extends IInterface {
    
    String DESCRIPTOR = "com.daking.aidl.IRemoteService";

    int TRANSACTION_getName = (IBinder.FIRST_CALL_TRANSACTION + 0);
    
    public String getName() throws RemoteException;
}
1
2
3
4
5
6
7
8

接着,实现一个对外提供服务的跨进程调用对象Stub。

public abstract class Stub extends Binder implements IRemoteService {

    public Stub() {
        this.attachInterface(this, IRemoteService.DESCRIPTOR);
    }

    public static IRemoteService asInterface(IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        IInterface iin = obj.queryLocalInterface(IRemoteService.DESCRIPTOR);
        if (((iin != null) && (iin instanceof IRemoteService))) {
            return ((IRemoteService) iin);
        }
        return new Proxy(obj);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    
    @Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(IRemoteService.DESCRIPTOR);
                return true;
            }
            case IRemoteService.TRANSACTION_getName: {
                data.enforceInterface(IRemoteService.DESCRIPTOR);
                java.lang.String _result = this.getName();
                reply.writeNoException();
                reply.writeString(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
}
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

然后,创建远程服务的代理对象Proxy。

class Proxy implements IRemoteService {
    private IBinder mRemote;

    Proxy(IBinder remote) {
        mRemote = remote;
    }

    @Override
    public IBinder asBinder() {
        return mRemote;
    }

    public String getInterfaceDescriptor() {
        return IRemoteService.DESCRIPTOR;
    }

    @Override
    public String getName() throws RemoteException {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();
        String _result;
        try {
            _data.writeInterfaceToken(IRemoteService.DESCRIPTOR);
            mRemote.transact(IRemoteService.TRANSACTION_getName, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readString();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}
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

之后,Server进程构建一个android.app.Service子类,为Client进程提供服务,并在这个Service子类中实现Stub

/* RemoteService */
public class RemoteService extends Service {
    public RemoteService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    private final Stub mBinder = new Stub() {
        @Override
        public String getName() throws RemoteException {
            return "RemoteService from server process";
        }
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

最后,Client进程绑定RemoteService服务,随后通过Stub.asInterface()将服务绑定成功回调的IBinder对象转换成IRemoteService对象。

/* Client */
IRemoteService mService;

public void onServiceConnected(ComponentName name, IBinder service) {
    mService = Stub.asInterface(service);
}
1
2
3
4
5
6

通过这个IRemoteService对象可调用Server的功能。

  • 如果Client和Server在同一个进程,直接调用本地的Stub实现类对象的方法。

  • 如果Client和Server在不同的进程,通过Proxy代理对象来完成远程的调用。

Proxy远程调用的过程如下:

  1. Client调用Proxy对象的某个方法。

  2. Proxy对象的方法内部将要访问的Server名称、要调用的方法ID和传参等信息通过transact()传递给Binder Driver。

  3. Binder Driver找到对应的Server,并将Client的信息传输给它。

  4. Server的Stub实现对象的onTransact()处理请求,并将结果返回给Binder Driver。

  5. Binder Driver将Server的处理结果返回给Client。

参考资料

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