1、为什么需要binder
Android 系统中,每个应用程序是由 Android 的 Activity,Service,Broadcast, ContentProvider 这四剑客的中一个或多个组合而成,这四剑客所涉及的多进程 间的通信底层都是依赖于 Binder IPC 机制。例如当进程 A 中的 Activity 要向进 程 B 中的 Service 通信,这便需要依赖于 Binder IPC。不仅于此,整个 Android 系统架构中,大量采用了 Binder 机制作为 IPC(进程间通信)方案,当然也存 在部分其他的 IPC 方式,比如 Zygote 通信便是采用 socket。
2、IPC原理
从进程角度来看 IPC 机制
每个 Android 的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个 4GB 的虚拟地址空间,其中 3GB 是用户空间,1GB 是内核空间,当然内核空间 的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共 享的,而内核空间却是可共享的。Client 进程向 Server 进程通信,恰恰是利用 进程间可共享的内核内存空间来完成底层通信工作的,Client 端与 Server 端进 程往往采用 ioctl 等方法跟内核空间的驱动进行交互。
3、Binder原理
Binder 通信采用 C/S 架构,从组件视角来说,包含 Client、Server、 ServiceManager 以及 binder 驱动,其中 ServiceManager 用于管理系统中的各 种服务。架构图如下所示:
可以看出无论是注册服务和获取服务的过程都需要 ServiceManager,需要注意 的是此处的 Service Manager 是指 Native 层的 ServiceManager(C++),并非 指 framework 层的 ServiceManager(Java)。ServiceManager 是整个 Binder 通 信机制的大管家,是 Android 进程间通信机制 Binder 的守护进程,要掌握 Binder 机制,首先需要了解系统是如何首次启动 Service Manager。当 Service Manager 启动之后,Client 端和 Server 端通信时都需要先获取 Service Manager 接口, 才能开始通信服务。 图中 Client/Server/ServiceManage 之间的相互通信都是基于 Binder 机制。既然 基于 Binder 机制通信,那么同样也是 C/S 架构,则图中的 3 大步骤都有相应的 Client 端与 Server 端。
- 注 册 服 务 (addService) : Server 进 程 要 先 注 册 Service 到 ServiceManager。该过程:Server 是客户端,ServiceManager 是服务端。
- 获 取 服 务 (getService) : Client 进 程 使 用 某 个 Service 前 , 须 先 向 ServiceManager 中获取相应的 Service。该过程:Client 是客户端, ServiceManager 是服务端。
- 使用服务:Client 根据得到的 Service 信息建立与 Service 所在的 Server 进程通信的通路,然后就可以直接与 Service 交互。该过程:client 是客 户端,server 是服务端。
图中的 Client,Server,Service Manager 之间交互都是虚线表示,是由于它们彼 此之间不是直接交互的,而是都通过与 Binder 驱动进行交互的,从而实现 IPC 通信方式。其中 Binder 驱动位于内核空间,Client,Server,Service Manager 位 于用户空间。Binder 驱动和 Service Manager 可以看做是 Android 平台的基础 架构,而 Client 和 Server 是 Android 的应用层,开发人员只需自定义实现 client、 Server 端,借助 Android 的基本平台架构便可以直接进行 IPC 通信。
3.1、C/S模式
BpBinder(客户端)和 BBinder(服务端)都是 Android 中 Binder 通信相关的代表, 它们都从 IBinder 类中派生而来,关系图如下:
四、Binder面试题全解析
Binder 是一种进程间通信机制,做 Android 开发肯定离不开跟 Binder 打交道在面试中 Binder 也是经常被问的一个点,那么本篇文章就以问答的方式,带你 了解一下关于 Binder 的重要知识点。
1.Binder 有什么优势
1、性能方面
共享内存 0 次数据拷贝
Binder 1 次数据拷贝
Socket/管道/消息队列 2 次数据拷贝
2、稳定性方面
Binder:基于 C/S 架构,客户端( Client)有什么需求就丢给服务端(Server)去完成
架构清晰、职责明确又相互独立,自然稳定性更好
共享内存:虽然无需拷贝,但是控制复杂,难以使用
从稳定性的角度讲,Binder 机制是优于内存共享的。
3、安全性方面
传统的 IPC 没有任何安全措施,安全依赖上层协议来确保。
传统的 IPC 方法无法获得对方可靠的进程用户 ID/进程 UI(UID/PID),从而无法别对方身份。
传统的 IPC 只能由用户在数据包中填入 UID/PID,容易被恶意程序利用。
传统的 IPC 访问接入点是开放的,无法阻止恶意程序通过猜测接收方地址获得连续
Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。
2.Binder 是如何做到一次拷贝的
主要是因为 Linux 是使用的虚拟内存寻址方式,它有如下特性:
- 用户空间的虚拟内存地址是映射到物理内存中的
- 对虚拟内存的读写实际上是对物理内存的读写,这个过程就是内存映射
- 这个内存映射过程是通过系统调用 mmap()来实现的
Binder 借助了内存映射的方法,在内核空间和接收方用户空间的数据缓存区之间做 了一层内存映射,就相 当于直接拷贝到了接收方用户空间的数据缓存区,从而减少 了一次数据拷贝
3.MMAP 的内存映射原理了解吗
MMAP 内存映射的实现过程,总的来说可以分为三个阶段:
(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
- 进程在用户空间调用库函数 mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
- 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址
- 为此虚拟区分配一个 vm_area_struct 结构,接着对这个结构的各个域进行了初始化
- 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中
(二)调用内核空间的系统调用函数 mmap(不同于用户空间函数),实现文件 物理地址和进程虚拟地址的一一映射关系
- 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找 到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文 件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。
- 通过该文件的文件结构体,链接到 file_operations 模块,调用内核函数 mmap,其原 型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。
- 内核 mmap 函数通过虚拟文件系统 inode 模块定位到文件磁盘物理地址。
- 通过 remap_pfn_range 函数建立页表,即实现了文件地址和虚拟地址区域的映射关 系。此时,这片虚拟地址并没有任何数据关联到主存中
(三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理 内存(主存)的拷贝
注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数 据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。
进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一 段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有 拷贝到内存中,因此引发缺页异常。
- 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
- 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则 调用 nopage 函数把所缺的页从磁盘装入到主存中。
- 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时 间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。
注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调 用 msync()来强制同步, 这样所写的内容就能立即保存到文件里了。
4.Binder 机制是如何跨进程的
1.Binder 驱动
- 在内核空间创建一块接收缓存区,
- 实现地址映射:将内核缓存区、接收进程用户空间映射到同一接收缓存区
2.发送进程通过系统调用(copy_from_user)将数据发送到内核缓存区。由于内 核缓存区和接收进程用户空间存在映射关系,故相当于也发送了接收进程的用户 空间,实现了跨进程通信。
5.说说四大组件的通信机制
1.activity
(1)一个 Activity 通常就是一个单独的屏幕(窗口)。
(2)Activity 之间通过 Intent 进行通信。
(3)android 应用中每一个 Activity 都必须要在 AndroidManifest.xml 配置文件中 声明,否则系统将不识别也不执行该 Activity。
2.service
(1)service 用于在后台完成用户指定的操作。service 分为两种:
- started(启动):当应用程序组件(如 activity)调用 startService()方法启动服务时, 服务处于 started 状态。
- bound(绑定):当应用程序组件调用 bindService()方法绑定到服务时,服务处于 bound 状态
(2)startService()与 bindService()区别:
- started service(启动服务)是由其他组件调用 startService()方法启动的,这导致服务 的 onStartCommand()方法被调用。当服务是 started 状态时,其生命周期与启动它的 组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此, 服务需要在完成任务后调用 stopSelf()方法停止,或者由其他组件调用 stopService() 方法停止。
- 使用 bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服 务也就终止,大有“不求同时生,必须同时死”的特点。
(3)开发人员需要在应用程序配置文件中声明全部的 service,使用 <service></service>标签。
(4)Service 通常位于后台运行,它一般不需要与用户交互,因此 Service 组件没有 图形用户界面。Service 组件需要继承 Service 基类。Service 组件通常用于为其他 组件提供后台服务或监控其他组件的运行状态。
3.content provider
(1)android 平台提供了 Content Provider 使一个应用程序的指定数据集提供给 其他应用程序。其他应用可以通过 ContentResolver 类从该内容提供者中获取或 存入数据。
(2)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录 数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数 据访问方式。
(3)ContentProvider 实现数据共享。ContentProvider 用于保存和获取数据,并 使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为 android 没有提供所有应用共同访问的公共存储区。
(4)开发人员不会直接使用 ContentProvider 类的对象,大多数是通过 ContentResolver 对象实现对 ContentProvider 的操作。
(5)ContentProvider 使用 URI 来唯一标识其数据集,这里的 URI 以 content:// 作为前缀,表示该数据由 ContentProvider 来管理。
4.broadcast receiver
(1)你的应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电 话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界 面。然而,它们可以启动一个 activity 或 serice 来响应它们收到的信息,或者用 NotificationManager 来通知用户。通知可以用很多种方式来吸引用户的注意力, 例如闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标, 用户可以打开它并获取消息。 (2)广播接收者的注册有两种方法,分别是程序动态注册和 AndroidManifest 文 件中进行静态注册。
(3)动态注册广播接收器特点是当用来注册的 Activity 关掉后,广播也就失效了。 静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也 是打开着的。也就是说哪怕 app 本身未启动,该 app 订阅的广播在触发时也会对 它起作用。
7.为什么 Intent 不能传递大数据
Intent 携带信息的大小其实是受 Binder 限制。数据以 Parcel 对象的形式存放在 Binder 传递缓存中。如果数据或返回值比传递 buffer 大,则此次传递调用失败并 抛出 TransactionTooLargeException 异常。 Binder 传递缓存有一个限定大小,通常是 1Mb。但同一个进程中所有的传输共享 缓存空间。多个地方在进行传输时,即时它们各自传输的数据不超出大小限制, TransactionTooLargeException 异常也可能会被抛出。在使用 Intent 传递数据时, 1Mb 并不是安全上限。因为 Binder 中可能正在处理其它的传输工作。不同的机 型和系统版本,这个上限值也可能会不同。