IPC为 (Inter-Process Communication) 缩写,称为进程间通信或跨进程通信,指两个进程间进行数据交换的过程。安卓中主要采用 Binder 进行进程间通信,当然也支持其他 IPC 方式,如:管道,Socket,文件共享,信号量等。
1.为什么使用Binder?
性能方面:
在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进程通信对通信机制的性能有严格的要求,Binder相对于传统的Socket方式,更加高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。
Binder传输数据的过程:一次拷贝
Socket传输数据的过程:两次拷贝
安全方面:
传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如 Socket 通信的IP地址是客户端手动填入,很容易进行伪造。然而,Binder机制从协议本身就支持对通信双方做身份校检,从而大大提升了安全性。
2.Binder原理
从进程角度来看IPC(Interprocess Communication)机制
每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。例如,对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间。当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的。Client端与Server端进程往往采用ioctl等方法与内核空间的驱动进行交互。
Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及Binder驱动,其中ServiceManager用于管理系统中的各种服务。架构图如下所示:
Binder通信的四个角色
ServiceManager 的作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 名字获得对 Server中Binder 实体的引用。
驱动负责进程之间 Binder 通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
3.Binder间数据传递
进程间数据传递都是需要序列化的。Serializable 和 Parcelable 接口都可以完成序列化,通过 Intent 和 Binder传输数据时候就需要使用 Serializable 和 Parcelable 。
区别和联系:
Serializable 是 Java中提供的接口,序列化和反序列化需要大量的 I/O 操作,开销比较大。而 Parcelable 是安卓中的序列化方式,效率高。Parcelable 主要用在内存序列化上,Serializable 主要用在用于文件,网络中传递。
Binder 是 Android 中的一个类,实现了 IBinder 接口。
从 IPC 角度来说
Binder 是 Android 中的一种跨进程通信方式,可以理解为一种虚拟的物理设备,它的设备驱动为 /dev/binder。
从 Framework 的角度:
Binder 是 ServiceManager 连接各种 Manager(ActivityManager,WindowManager) 和 ManagerService 的桥梁。
从应用层的角度
Binder 是客户端和服务端进行通信的媒介,当 bindService 时,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或者数据,包括普通服务和 AIDL 服务。
1.使用 Bundle
Bundle 实现了 Parcelable 接口,可以通过 Intent 进行传递。当从一个进程启动另一个进程的 Activity 时,可以在 Bundle 中附加参数,这些数据必须是能够被序列化的。
2.使用文件共享
A进程往文件中写数据,B进程从文件中读数据。在 Windows上,一个文件如果被加了排斥锁将会导致其他线程无法对其进行访问。而 Andorid 是基于 Linux,是的其支持并发 读/写 可以无限制的进行,但是需要注意并发安全问题。
SharedPreferences 是 Android中轻量级的存储方案,通过键值对来存储数据,采用 xml 文件来保存键值对。因为 Android 系统对 读/写 有一定的缓存策略,即在内存中会有一份 SharedPreferences 文件的缓存,在多进程下,系统对它的读写变得不可靠。SharedPreferences 的 get 个 put 方法都是经过 synchronized 修饰的。
3.使用 Messenger
Messenger 也被翻译为信使,通过它可以在不同的进程间传递 Message 对象,在 Message 中放入我们需要传递的数据。它的底层也是常用 AIDL。
服务端通过 Handler 创建一个 Messenger,然后在 Servce 的 onBind 方法中返回这个 Messenger 对象的底层的 Binder即可。客户端通过服务端返回的 IBinder 就可以创建 Messenger来服务端发消息了。
4.使用 AIDL
AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于IPC的代码。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板
进程间的通信,速度快(系统底层直接是共享内存),性能稳,效率高,一般进程间通信就用它. AIDL是Binder机制向外提供的接口,目的就是为了方便对Binder的使用.
5.使用 ContentProvider
ContentProvider 是 Android 提供的专门用于不同应用间进行数据共享的方式,它的底层实现也是 Binder。
6.使用 Socket
Socket 也被称为 “套接字”,分为流式套接字和用户数据报套接字两种,分别对应于网络传输控制层中的 TCP 和 UDP协议。
IPC各种方式比较
在这种模式下,整个工作机制是这样的;每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。由此可见,Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程,它的工作原理如图所示。
定义接口
Binder机制通常用于实现进程间的接口调用,我们需要先定义Binder接口。一个典型的Binder接口如下:
public interface IBookManager {
public void addBook(Book book);
public void deleteBook(int bookId);
public Book getBook(int bookId);
public List getBooks();
}
这是一个书管理的Binder接口,包含增加书籍、删除书籍、获取书籍等方法。
实现Binder接口
然后我们需要实现上面的Binder接口,并继承自Binder类,这样这个类的实例就代表了一个Binder实体,可以跨进程传输。
public class BookManager extends Binder implements IBookManager {
public void addBook(Book book) { ... }
public void deleteBook(int bookId) { ... }
public Book getBook(int bookId) { ... }
public List getBooks() { ... }
}
在Server端注册Binder实体
在提供服务的进程内,我们需要将Binder实体注册到ServiceManager中。这样当其他进程想访问这个Binder实体时,就可以从ServiceManager中获取它。
public class BookManagerService extends Service {
private final BookManager bookManager = new BookManager();
public void onCreate() {
super.onCreate();
publishBinderService(CMD_BOOK_MANAGER, bookManager);
}
}
在onCreate()方法中我们通过publishBinderService()方法将bookManagerBinder实体发布到ServiceManager中,这样其他进程就可以通过CMD_BOOK_MANAGER这个key来获取这个Binder实体了。
在Client端获取Binder代理并调用
在客户端进程中,我们可以通过ServiceManager访问Server发布的Binder实体。
public void queryBookManager() {
// 获取ServiceManager的代理
IBinder service = ServiceManager.getService(CMD_BOOK_MANAGER);
// 通过服务端返回的IBinder对象生成Binder代理
IBookManager bookManager = IBookManager.Stub.asInterface(service);
// 通过代理调用服务端的方法
List books = bookManager.getBooks();
...
}
我们首先通过ServiceManager获取要访问的Binder实体,这个会返回一个IBinder对象。然后通过IBookManager.Stub.asInterface(service)将IBinder对象转成客户端所需的IBookManager接口类型,这个对象就是Binder代理。我们可以通过这个代理对象调用服务端IBookManager接口中的方法。
至此,我们完成了一个跨进程的Binder通信的全过程。客户端通过Binder代理访问服务器提供的服务,服务器通过Binder实体来处理客户端的请求。这整套机制都建立在Binder驱动之上,Binder驱动负责将双方的请求和回复进行传递。