FrameWork学习总结之Binder

Binder

Android IPC Binder机制

1.为什么 Android 要采用 Binder 作为 IPC 机制?

 Android系统的内核Linux已经有很多进程间通信的方式,比如:管道(Pipe)、信号(Signal)和跟踪(Trace)、插口(Socket)、报文队列(Message)、共享内存(Share Memory)和信号量(Semaphore)等 IPC 机制,那Android为何还要再实现一个Binder IPC 呢?主要是基于高效性稳定性安全性几方面原因。

  1.高效性:对于手机移动通信设备来说,是基于Client-Server即C/S架构的通信方式,而上面提到的Linux传统IPC中只有socket支持Client-Server的通信方式,但是socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。 

IPC方式

数据拷贝次数

共享内存

0

Binder

1

Socket/管道/消息队列

2

 2.稳定性:Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。所以从稳定性的角度讲,Binder 机制是也优于内存共享的复杂控制缺点。

  3.安全性:Android 作为一个开放性的平台,市场上有各类海量的应用供用户选择安装,因此安全性对于 Android 平台而言极其重要。作为用户当然不希望我们下载的 APP 偷偷读取我的通信录,上传我的隐私数据,后台偷跑流量、消耗手机电量。传统的 IPC 没有任何安全措施,完全依赖上层协议来确保。首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。

  Binder优势总结:

优势

描述

性能

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

稳定性

基于 C/S 架构,职责明确、架构清晰,因此稳定性好

安全性

为每个 APP 分配 UID,进程的 UID 是鉴别进程身份的重要标志

Binder是什么?

机制:Binder是一种进程间通信的机制

驱动:Binder是一个虚拟物理设备驱动

应用层:Binder是一个能发起进程间通信的JAVA类

对于server进程来说,binder指的是binder本地对象;对于client来说,binder指的是binder代理对象。 对于传输过程而言,binder是可以跨进程传递的对象。

Binder就是Android中的血管,在Android中我们使用Activity,Service等组件都需要和AMS(system_server)进行通信,这种跨进程的通信都是通过Binder完成。Android中ContentProvider、Intent、aidl都是基于Binder. .

Activity,Service等组件和AMS不是同一个进程,其实也是多进程通信。

为什么要用多进程?

虚拟机给每一个进程分配的内存是有限制的,LMK会优先回收对系统资源占用多的进程

为了突破内存限制,防止占用内存过多被杀

功能稳定性,一个进程崩溃对另外进程不造成影响:将不稳定功能放入独立进程

规避内存泄漏,独立的WebView进程阻隔内存泄漏导致问题

Android系统中,涉及到多进程间的通信底层都是依赖于Binder IPC机制。

Binder是如何实现跨进程通信的

首先需要了解为啥有跨进程通讯。

Linux 为了保护进程空间不被别的进程破坏或者干扰,进程是相互独立的(进程隔离),而且一个进程空间还分为用户空间和内核(Kernel)空间,相当于把 Kernel 和上层的应用程序抽像的隔离开。这里有两个隔离,一个进程间是相互隔离的,二是进程内有用户和内核的隔离。

即然有隔离,那么它们之前要相互配合时就得有合作(交互)。进程间的交互就叫跨进程通讯或进程间通信(IPC Inter-Process Communication,进程间通信),而进程内的用户和内核的交互就是系统调用。

用户空间访问内核空间的唯一方式就是系统调用;通过这个统一入口接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。

也就是说,为了保证安全性和独立性,一个进程是不能直接操作或者访问别一个进程空间的。Android 即然是架设在 Linux 基础之上的,当然也要解决这个进程间通信的问题。

Binder通信采用C/S架构,包含Client、 Server、 ServiceManager 以及 Binder 驱动。在 framework 层进行了封装,通过 JNI 技术调用 Native(C/C++)层的 Binder 架构,在 Native 层以 ioctl 的方式与 Binder 驱动通讯。

Client进程

使用服务的进程。

Server进程

提供服务的进程。

ServiceManager进程

Android OS的整个服务的管理程序,在binder通信中ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。

任何service在被使用之前,均要向SM(Service Manager)注册。当客户端需要访问某个service时,应该首先向SM查询是否存在该服务。如果SM存在这个service,那么会将该service的handle返回给client,handle是每个service的唯一标识符。

Binder

一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。所以,在进程间通信时处理并发问题时,如使用ContentProvider时,它的CRUD(创建、检索、更新和删除)方法只能同时有16个线程同时工作

Binder驱动

驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

一个Binder驱动中有很多个Binder对象,通过InterfaceToken区分他们

Binder通信

了解进程间通信的人都知道在Android使用的是Binder进行进程间通信,它的核心是mmap memory mapping,即内存映射的方式进行的跨进程通信。

一次Binder通信的基本原理流程是什么样的

接下来我们正式介绍下 Binder IPC 的原理。

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

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

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

那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?显然不是,否则也不会有开篇所说的 Binder 在性能方面的优势了。

这就不得不通道 Linux 下的另一个概念:内存映射

Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

2 Binder IPC 实现原理

Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。

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

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

一次完整的 Binder IPC 通信过程通常是这样:

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

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

  1. 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

如下图:

FrameWork学习总结之Binder_第1张图片

注:一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来

  • 进程间,用户空间的数据不可共享

  • 进程间,内核空间的数据可共享。

Binder 通信过程

  • 首先,一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;

  • Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。

  • Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

我们看到整个通信过程都需要 Binder 驱动的接入。下图能更加直观的展现整个通信过程(为了进一步抽象通信过程以及呈现上的方便,下图我们忽略了 Binder 实体及其引用的概念):

FrameWork学习总结之Binder_第2张图片

题外话

为什么Activity传递对象需要序列化?

什么是序列化和反序列化

  • 序列化:把对象转换为字节序列的过程称为对象的序列化。

  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

序列化的原因基本三种情况:

  • 永久性保存对象,保存对象的字节序列到本地文件中;

  • 对象在网络中传递;

  • 对象在 IPC 间传递。

Android中的两种序列化机制
  • 实现 Serializable 接口

  • 实现 parcelable 接口

两种序列化方式的区别:
  • Serializeble 是 java 的序列化方式,Parcelable 是 Android 特有的序列化方式;

  • 在使用内存的时候,Parcelable 比 Serializable 性能高,所以推荐使用 Parcelable。

  • Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。

  • Parcelable 不能使用在要将数据存储在磁盘上的情况,因为 Parcelable 不能很好的保证数据的持续性在外界有变化的情况下。尽管 Serializable 效率低点, 也不提倡用,但在这种情况下,还是建议你用 Serializable。

  • Serializeble 序列化的方式比较简单,直接集成一个接口就好了,而 parcelable 方式比较复杂,不仅需要集成 Parcelable 接口还需要重写里面的方法。

Intent 在启动其他组件时,会离开当前应用程序进程,进入 ActivityManagerService 进程 – intent.prepareToLeaveProcess()。 这也就意味着,Intent 所携带的数据要能够在不同进程间传输。

首先我们知道,Android 是基于 Linux 系统,不同进程之间的 java 对象是无法传输,所以我们此处要对对象进行序列化,从而实现对象在 应用程序进程 和 ActivityManagerService 进程 之间传输。

四大组件底层的通讯机制是怎样的?

通过内核模块(Binder 驱动)来实现通信

AIDL内部实现的原理

AIDL 是 Android Interface Definition Language 的简写,即 Android 接口定义语言。

Android 系统为每一个应用开启一个独立的虚拟机,每个应用都运行在各自进程里(默认情况下),彼此之间相互独立,无法共享内存。当一个应用想要访问另一个应用的数据或调用其方法,就要用到 Android 系统提供的 IPC 机制。而 AIDL 就是 Android 实现 IPC 机制的方式之一。

aidl其实就是利用Binder来实现跨进程调用。而这个系统根据.aidl文件生成的java文件就是对binder的transact方法进行封装。

onTransact方法是提供给server端用的,transact方法(内部类proxy封装了transact方法)和asInterface方法是给client端用的。系统自动生成不知道你要把哪个当做server端哪个当做client端所以就都生成了。

DESCRIPTION是根据aidl文件位置(包名)来生成的,DESCRIPTION是binder的唯一标识,这也解释了为什么要保证client端和server端.adil文件的包结构一样;

aidl的transact的调用会导致当前线程挂起,因此如果Server端的方法耗时最好另起线程来调用transact方法;

aidl通过Parcel来传递参数和返回值,因为binder只支持Binder对象和parcel对象的跨进程调用;

Binder,IBinder,IInterface三个的关系。Binder是继承自IBinder对象的,IBinder是远程对象的基本接口。另外IBinder的主要API是transact(),与它对应另一方法是Binder.onTransact(),而IInterface主要为aidl服务,里面就一个方法——asBinder,用来获得当前binder对象(方便系统内部调用)。

最后说白了aidl的核心就是client调用binder的transact方法,server通过binder的onTransact方法了来执行相应的方法并返回。

你可能感兴趣的:(Framework,android)