Handler
Handler是Android系统中的一种消息传递机制,起作用是应对多线程场景。将A进程的消息传递给B线程,实现异步消息处理。很多情况是将工作线程中需要更新UI的操作消息传递给UI主线程,而实现更新UI操作。
因为工作线程和主线程是共享地址空间,即Handler实例对象mHandler位于线程间共享的内存堆上,工作线程和主线程直接使用该对象,只需要注意多线程的同步问题。工作系统通过mHandler向其成员变量MessageQueue中添加Message,而主线程一直处于loop中,当收到新的message时,按照一定规则分发给相应的handlerMessage方法来处理。
Handler用于同进程的线程间通信的核心是线程间共享内存空间,而不同矜持拥有不同的地址空间,也就无法使用handler来实现进程间通信。如图1所示,为Handler消息通信架构图。
图中,首先Handler通过sendMessage()发送Message到MessageQueue队列,Looper通过looper()不断提取出达到触发条件的Message,并将Message交给target来处理;然后通过displatchMessage()分发给handlerMessage处理。
将Message添加到MessageQueue时,会往管道中写入字符,这样就回唤醒loop线程;如果MessageQueue中没有Message,并处理idle状态,则会执行idleHandler接口中的方法,往往用于做一些清理性的工作。关于消息分发的优先级:
- Message回调分发:message callback.run()优先级最高。
- Handler回调分发:Handler.mCallback.handleMesage(msg),优先级次之。
- Handler默认分发:Handler.handlerMessage(msg),优先级最低。
Binder
概述
Android中一般每个应用对于一个进程,而涉及到每个应用之间的通信,即进程间通信,Android中采用的最多的IPC机制为Binder机制。首先我们介绍下IPC机制原理,如图2所示为从进程角度阐述了IPC机制。从图中可以看出,每个Android进程只能运行在自己所有的虚拟地址空间中。对于一个4G的虚拟地址空间,假设用户空间为3G,内核空间为1G(可修改)。进程在用户空间的数据是不可共享的,在内存空间中则可共享。Client进程想server进程发送信息,即利用内核空间可共享机制完成。
- 从IPC角度:Binder是Android中的一种跨进程通信方法,该方法是Android系统独有的。
- 从Android Driver层:Binder还可以理解为一种虚拟的物理设备,设备驱动为/dev/binder。
- 从Android native层:Binder是创建Service Manager以及BpBinder/BBinder模型,大家与Binder驱动的桥梁。
- 从Android frameworks层:Binder是各种Manager(ActivityManager、WindowManager)和相应xxxManagerService的桥梁。
- 从APP层:Binder是客户端和服务端进程通信的媒介,当bindServicer时,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获得服务端提供的服务(包括普通服务和基于AIDL的服务)或数据。
架构
如图3所示,为Binder在Android不同层级的位置和关联图,是一种C/S架构的通信机制。在Kernel层,Binder可以看成是一个驱动,其驱动架构与其他驱动相同。Native层中ServiceManager会启动一个Binder的守护进程,ServiceManager功能简单,包括获取服务、注册服务,大部分同行通过都存在于BpBinder和BBinder之间。Framework层的Binder逻辑是建立在Native层架构基础上的,核心逻辑都是交给Native层来处理。但需要注意下frameworks层的Binder逻辑是建立在Native层架构基础上的,核心逻辑都是交给Native层来处理。但需要注意下frameworks层的ServiceManager和Native层的ServiceManager功能并不对应,其最终实现是通过BinderProxy传递给Native层来完成的。
Socket
Socket通信也是基于C/S架构,但相比Binder要简单一些。首先我们来回顾下TCP/IP协议的知识。TCP/IP协议是一个四层的体系结构:应用层、传输层、网络层、网络接口层。在传输层中又有TCP和UDP两个协议。Socket是工作与TCP/IP协议中应用层和传输层之间的一种抽象。Android系统中,又分为流套接字和数据报套接字。其中流套接字将TCP协议作为其端对端协议,提供了一个可信赖的字节流是服务;数据报套接字使用UDP协议,提供数据打包发送服务。在Android系统中使用Socket通信的场景包括:
- Zygote:fork新进程,system_server向zygote发送fork新进程请求是使用socket通信。
- Installd:用户安装app的守护进程,PMS安装应用时向installd发送socket通信来完成安装过程。
- Adbd:用于服务adb操作。
- Logcatd:用于服务logcat操作
- Vold:存储类守护进程,用于服务USD、sdcard等存储设备的事件处理。(在Android9.0中已将这一进程通信修改为Binder实现)
其他几种通信机制
管道
Linux支持的最初Unix IPC机制之一,特点是:
- 管道是半双工,数据只能向一个方向流动,需要双方通信时,建议使用两个管道。
- 只能用于父子进程或者兄弟进程之间。
- 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且每次都是从缓冲区都不读出数据。
- 数据读出和写入;一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区头部读出数据。
消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接受进程可以独立地接受含有不同类型的数据结构。通过发送消息来避免明明管道的同步和阻塞问题。消息队列发送的消息有最大长度限制。
共享内存
共享内存,顾名思义即允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递的一种非常有效的方法。不同进程之间共享的内存通常安排为同一段物理内存。进程可以讲同一段共享内存链接到他们自己的地址空间中。所有进程都可以访问共享内存中的地址。
需要注意的是:共享内存没有使用同步机制,即进程A对共享内存进程写操作时,并没有对共享内存加锁,进程B仍然可以读取共享内存中的数据。所以通常我们需要使用其他的机制来同步共享内存的访问。
信号量
为了防止出现多个进程同步访问一个共享资源而引发一系列问题,我们需要一种方法,它可以通常生成令牌来授权,在任何时刻只能有一个指向线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式执行。而信号量就可以提供这样的一种访问机制,也就是说信号量是用来协调进程对资源共享的访问。
信号量是一个特殊变量,程序对其访问都是原子操作,且只允许对它进程等待和发送信息操作。最简单的信号量是只能取0和1的变量。这也是信号量最常见的形式。这就是二进制信号量。而中可以取多个正整数的信号量称为通用信号量。
信号
信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会响应地采取一些行动。通常信号是由一个错误产生的。但他们还可以作为进程间通信或修改行为的一种形式,明确地由一个进程发送给另一个进程。信号产生叫做生成,接收叫做信号捕获。
Android中几种IPC机制对比
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 组件间进程间通信 |
文件共享 | 简单易用 | 不适合并发场景,并且无法做到进程间即时通信 | 无并发访问情形,交换简单数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用复杂,需要处理好线程同步 | 一对多通信且有RPC需求 |
Message | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好处理高并发场景,不支持RPC,数据通过Message进程传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求 |
ContentProvider | 在数据访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 | 可以理解为受约束AIDL,主要提供数据源的CRUD操作 | 对多进程间的数据共享 |
socket | 功能强大,可以使用网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不支持直接RPC | 网络数据交换,或少数进程间交换 |