Binder概览

Binder概览翻译

基础理论

多任务、进程、和线程

多任务是指同时执行多个程序或进程实例的能力。操作系统为每一个二进制执行文件创建了一份内存,包含了栈区、堆区、数据区和共享映射文件,他还分配了特殊的内部管理结构,叫做进程。

操作系统必须提供公平的比例,因为同时只有一个进程可以使用CPU。所有进程都必须是可中断的,在他们的时间片内,操作系统会发送“睡眠”或“唤醒”信号给他们,这项工作是由调度器(scheduler)完成的,调度器提供给每一个进程最佳的时间片。

线程是一种在内存中没有独自地址空间的进程,它和父进程共享内存地址空间。进程可以有多个子进程,并且必须隶属于一个进程。

进程隔离

由于保密和安全的原因,一个进程不能操作其他其他进程的数据。为了达到这一目的,操作系统必须集成一个进程隔离的概念。在Linux中,虚拟内存机制通过将每个进程访问分配给一个线性和连续的内存空间来实现,这个虚拟内存空间通过操作系统映射到物理内存。每一个进程拥有自己的虚拟内存空间,所以每一个进程不能操作其他进程的内存空间。进程中的内存访问被限制到它自己的虚拟内存空间中,只有操作系统可以访问所有的物理内存。

进程隔离确保每个进程的内存安全,但是在很多情况下,两个进程之间的通信是需要并且是必须的。操作系统必须提供一种机制来支持进程间通信。

用户空间和内核空间

正常情况下,进程运行在权限限制的操作模式下,这意味着它们无法访问物理内存和设备。这种运行模式叫做“用户空间”。更抽象地说,操作系统的安全边界的概念引入了术语“环”。注意,这必须要硬件平台提供支持。一组权限被分配给一个环。英特尔的硬件支持四个环,但是Linux系统只用到了两个环,分别是0号环和3号环,0代表全部权限,3代表最小权限。环1和环2没有使用。系统进程属于环0,用户进程属于环3.如果一个进程需要更高的权限,必须由环3转换到环0.这个转换经过一个网关,对参数进行安全检查,这种转换成为“系统调用”,并产生一定量的计算开销。

Linux系统中的进程间通信

如果一个进程需要和其他进程交换数据,这个过程叫做“进程间通信(IPC)”,Linux系统提供了多种机制来支持IPC:

信号 最早的IPC方式。一个进程可以发送信号给相同uid和gid,或同一进程组的进程。

管道 管道是单项字节流,将一个进程的标准输出与另一个进程的标准输入相连接。

套接字 套接字是双向通信的端点。两个进程可以通过打开相同的套接字来与字节流进行通信。

消息队列 一个进程可以向消息队列中写入一个可读的消息,其他的进程可以读取这个消息。

扫描二维码关注公众号,回复: 1732137 查看本文章

信号量 信号量是一个可以被许多进程读写的共享变量。

共享内存 将系统内存地址映射到两个进程都可以访问的虚拟地址空间中。

Android

Android操作系统诞生于2008年,由开放手机联盟和Google开发。

内核

Android操作系统基于Linux2.6标准内核,但是为了手机需要,增强一些新组件,像一个内核模块 闹钟,匿名共享内存,Binder,电源管理,低内存清理器、内核调试器和日志。Binder提供了在Linux上一种新的IPC机制。

编程语言

系统开发可以使用四种语言:汇编、C语言、C++语言和Java。系统内核有少量的汇编,主要是由C编写。一些原生应用和依赖库由C++编写。所有其他应用尤其是自定义应用,由Java编写。

Java原生接口

由虚拟机编译的程序和为了在指定计算平台(英特尔X86或ARM)运行而编译的程序有一些区别。在指定平台编译的程序叫做原生代码,因为Java是在虚拟机中执行自己的字节码,不能直接执行原生代码。由于有访问更低级别操作系统机制的需求,例如内核调用,Java不得不克服这个障碍。Java原生接口(JNI)就是来满足这个需求的,JNI允许Java执行依赖库中编译好的其他语言编写的代码,像C++。这是获得访问系统功能和降低Java安全级别之间的折中方案。

Dalvik虚拟机

Dalvik虚拟机用来运行Java编写的应用。由于许可证的原因,Dalvik虚拟机不能要求成为一个Java虚拟机,但是目的是一样的。Java5版本的代码可以执行在Dalvik这个环境中。

Sun公司的JVM是基于栈的,因为栈虚拟机可以运行在所有的硬件上。Java主要设计准则就是硬件和平台无关。Dalvik虚拟机是基于寄存器的,性能更好并且更好的适配ARM硬件。这是两个不同的设计准则,充分利用硬件独立性来获取更高的性能和更小的电量损耗,这种设计更适用与电量有限的手机平台上。使用Java本地接口的可能性削弱了Java的安全保证属性,以隐式检查变量的边界并封装系统调用,并强制使用JVM定义的接口到系统。原生库的使用可以绕过虚拟机的类型和边界检查,从而打开了栈溢出攻击的大门。

尽管存在一些安全问题,对于进程间通信机制来说仍然是必须的,因为Binder的中间件是C++库并且必须可以被JNI访问。

Zygote进程

由于性能原因,Dalvik虚拟机仅被启动一次。每一个新的虚拟机实例都是被克隆出来的,这个操作是由一个叫做Zygote系统服务完成的。

首先,Zynote会在堆上预加载和初始化通用的Android类。然后,监听Socket是否有启动新的Android应用程序指令。一旦接受到启动的指令,Zygote会基于已加载的应用程序孵化出一个新的进程,这个进程成为启动的应用程序,并通过写时复制映射与原始Zygote进程共享堆,因此Zygote堆的内存页面被链接到这个新进程。尽管应用程序与Zygote共享堆并且是只读的,但当在堆上执行写操作时,响应的内存页面会被复制并链接到新的内存页上。现在就可以在不影响父进程Zygote的原始数据情况下,来操作堆数据了。

当一个Android应用程序被克隆出来后,它是使用了和Zygote一样的内存布局,因此每一个应用程序的内存布局都是一样的。

应用程序概览

每一个应用程序由四大组件组成,每一个组件都有一个特定的主题。

Activity在应用程序中代表用户界面,它负责操作屏幕并接受用户响应。Activity不应用来持有依赖数据,因为当有其他的Activity显示时,它有可能被操作系统休眠。

对于耗时的操作,Android提供了Service组件。应用程序中所有后台任务应当通过Service执行,因为一个前台Service不会轻易被终止,除非系统内存不存,应用程序需要被终止来释放内存。

尽管Service被用来支持耗时任务执行,它仍是轻量级的持有依赖数据。这里有一个ContentProvider的概念,它提供一个接口来访问类SQL数据库那样的文件或网络流等持久数据。

Broadcast Receiver用于接受全系统消息,例如,向所有应用程序提供有新短信的消息、电池电量低的警告也会通过广播发送。Broadcast Receiver处理这些消息并编组某些动作,例如在关机之前保存应用程序的状态。

Application manifest包含了应用程序用到的四大组件的信息。在这个文件中,设置了基本程序配置,例如一个Service在自己的进程中启动或是否依附到本地进程。

组件间通信概览

不同的组件间需要交换数据,这依赖于组件间通信或进程间通信(如果特定组件属于不同的进程)。这个通信工作由Intent实现。Intent作为将要执行操作的表示。一个Intent的基础数据结构包含一个URI和一个Action。URI唯一标识一个应用组件,Action标识将要执行的操作。

Intent通过进程间通信系统传递,下图中展示了不同形式的组件活动。一个Activity可以被一个Intent启动,并且使其他的Activity展示到前台。

一个Service可以通过IPC启动、停止和绑定。这些调用和返回方法都是由IPC实现。

Content Provider可以通过IPC被Activity查询,并返回相应结果。Android源码文件显示IPC被广泛的用来交换抽象数据。

Broadcast Receiver通过IPC获取所有已经订阅的Intent。

到这里,IPC机制的重要性开始显现。Android系统框架是一个分布式系统,实现这种设计的关键和主要技术是IPC Binder机制。

安全概览

Android中安全机制包含三层。基础层包含由永久内存分成的两个分区组成,成为系统和数据。系统分区挂载为只读,防止系统数据被篡改。数据分区是可以存储应用程序状态和持久性数据的地方。注意,App Store应用程序可以将系统分区重新挂载为写模式来安装新的应用程序。

为了区分每一个应用,使用Linux的自由访问控制模型。每个应用程序都有其唯一的用户ID和组ID,并且只能访问自己的目录。只有来自同一作者的应用才能在相同的用户标识下运行,并访问其数据。所有应用程序必须由作者签名,来保护数据不被篡改和识别身份。因此,系统可以确定应用程序是否来自同一作者,并且可以再一个UID下运行。

由于Linux DAC模型只允许一个基本的访问控制级别,为了获得精细的权限,Android的中间件提供了一个权限标签系统,它实现了一个强制访问控制模型。该系统基于一组权限和一组导出的服务,包括访问控制。

对于在操作系统上执行的每个操作,都存在权限标签。在安装时,应用程序要求用户提供一组权限,用户可以在授予所有权限或中止安装之间进行选择。一旦权限在安装是被授予,除非卸载应用程序,否则永远不会删除权限。

每一个应用可以指定一个Intent过滤器,一个白名单机制,定义了应用程序中的组件可以接受哪些类型的Intent。未被列出了Intent会被引用监视器过滤掉。

一个问题是,作者的应用程序可以自由沟通,因为相同的UID和GID。这意味着,如果手机上安装了同一作者的多个应用程序,则每个应用程序的不同权限将通过传递属性累积。应用程序A要求同一作者的应用程序B执行一个操作,对于该操作,应用程序A没有权限,但是应用程序B有.因此,应用程序B拥有不被用户授予的专用应用程序的权限。

Binder

背景

Binder最初是由Be Inc以及后来在Dianne Hackborn领导下的Palm Inc以OpenBinder的名字开发的。
它的文档声称OpenBinder是“……一个系统级的组件架构,旨在为传统的现代操作系统服务提供更丰富的高级抽象”。或者更具体地说,Binder有能力提供从一个执行环境到另一个执行环境的功能和数据的绑定。 OpenBinder实现在Linux下运行,并扩展了现有的IPC机制。 OpenBinder的文档声明:“代码运行在各种各样的平台上,包括BeOS,Windows和PalmOS Cobalt。

Android中的Binder是一个定制的OpenBinder实现。在Android的M3版本中,使用了OpenBinder的原始内核驱动程序部分,但由于OpenBinder的许可证与Android的许可证不兼容,用户空间部分必须重写。在Android的M5版本中,驱动程序也被重写了。核心概念保持不变,但许多细节已经改变。

原来的OpenBinder代码不再被开发。但是在某种程度上,对Android的重新实现可以看作是由Android开发者维护的一个分支。所以OpenBinder在Android Binder中存活下来。

Binder术语

Binder框架使用自己的术语来命名设施和组件。本节总结了最重要的术语,后面的章节将详细讨论这些细节。

Binder 这个词使用含糊不清。 Binder指的是整个Binder的体系结构,或者Binder指的是Binder接口的特定实现。

Binder Object Binder对象是实现了Binder接口的类的实例。一个Binder对象可以实现多个Binder接口。

Binder Protocol Binder中间件使用非常底层的协议来与驱动通信。

IBinder Interface Binder接口是一系列定义好的方法,属性和Binder可以实现的事件。它通常使用AIDL标记语言描述。

Binder Token 唯一标识Binder的数字值

能力

Binder框架不仅仅是提供了简单进程间通信的系统。下图列举出了Binder中的能力:

从Android应用程序员的角度来看,最重要的改进是远程对象的方法可以被调用,就好像他们在使用本地对象方法一样。这是通过同步方法调用实现的。因此,客户端调用进程在服务端进程响应之前将被阻塞。更高级的是,当使用异步消息返回时,客户端不需要提供回调。

这与Binder框架的功能有关,用于发送单向和双向消息以及启动和停止线程。

这是AIDL的一个特性,因此在更高层次上得到了解决,应用程序不需要知道服务是在服务器进程还是在本地进程中运行。 Android的应用程序概念使得可以在自己的进程或活动进程中运行服务。这使得应用程序开发人员可以轻松地将服务导出到其他Android应用程序,而无需查看代码。

Android系统服务使用Binder的特殊通知功能,即死亡链接机制。当某个进程的Binder被终止时,该工具允许进程得到通知。特别是,这是Android窗口管理器建立到每个窗口的回调绑定器接口的死亡关系的方式,以便在窗口关闭的情况下获得通知。

每个Binder是唯一可识别的,这意味着它可以作为共享令牌。在假定Binder不通过服务管理器发布的情况下,Binder标识仅由所涉及的通信方知晓,包括远程进程,本地进程和系统。因此,一个Binder可以被用作安全访问令牌。令牌也可以跨多个进程共享。

另一个安全功能是被请求进程可以通过UID和PID来识别青丘族进程。结合Android安全模型,可以确定一个进程。另一个功能,但在这篇文章中没有分析,是共享内存机制,通过Binder框架,一块堆内存可以被共享。

总而言之,Binder及其框架支持许多功能,以确保良好的面向对象的进程间通信。

概览

本节介绍Binder能力背后的概念。由于缺乏公开的文档,主要是通过审查源代码来获得的。

通信模型

Binder框架通信是一个客户端服务器模型。客户端将启动通信并等待来自服务器的响应。 Binder框架使用客户端代理进行通信。在服务器端,存在用于处理请求的线程池。

进程A是客户端,持有实现与Binder内核驱动程序通信的代理对象。进程B是服务器进程,具有多个Binder线程。 Binder框架将产生新线程来处理所有传入的请求,直到达到定义的最大线程数。代理对象正在与Binder驱动程序交谈,这会将消息传递给目标对象。

交互

如果一个进程将数据发送到另一个进程,则称为一次交互。随着每个传输有效载荷数据被提交。这些数据被称为交互数据。

数据的结构如图4.3所示。它包含一个目标,即目标绑定器节点。 Cookie字段用于内部信息。发件人ID字段包含安全相关信息。数据字段包含一个序列化数据数组。一个数组入口由一个命令及其参数组成,这个命令是通过Binder解析的。 Binder框架之上的应用程序现在可以定义自己的命令,并依赖于这个参数。正如从AIDL派生的Android源代码中可以看出,实现远程服务的开发人员使用target命令字段作为函数指针,并将其参数序列化到与函数信号对应的参数字段。除此之外,这个概念有助于实现用户定义的IPC协议。Binder通信依赖于请求和回复机制。这个事实限制了实现,这意味着每个请求必须有一个回复,并且请求进程必须等待它。它在等待时间被阻塞,不能进行。为了实现一般的可能的异步IPC协议,开发人员必须注意这一事实。如果一个开发者需要一个非阻塞的IPC,他必须实现一个管理机制来响应请求,Binder不提供这种异步功能。

因此,一次交互包含两个消息,一个请求消息和一个响应消息。Binder中的死亡通知特性是单向的通信。

Parcel容器和编码

在面向对象角度看,交互数据被叫做parcel,它是一个可传输的数据结构。任何对象想要被被远程传输,必须实现Parcelable接口。一个实现这个接口的对象必须提供方法,将发送方的对象的数据结构序列化,并在接收端进行恢复。所有信息必须简化为简单的数据类型,如Int,Float,Boolean和String类型。这部分信息被发送者串行写入一个包裹,由接收者读取和重建。

构建Parcel的过程叫做编码或序列化一个对象。相反的,从Parcel中重建一个对象的过程叫做解码或反序列化一个对象。

Binder的内存共享特性不能被Java API封装器应用程序使用,只有本地C ++库可以访问内存中的共享对象表示。

死亡通知

Binder框架支持Binder对象死亡的通知。这是通过观察者模式实现的。一个本地的Binder对象,有兴趣知道一个远程Binder对象的终止,将自己添加到观察者列表中。如果事件发生,保存远程Binder对象的进程终止,则通知本地对象并可以作出反应,下图是从源代码中分析出来的,是一个流程图,可视化模式。

上下文管理器

上下文管理器是Binder框架的一个特殊的Binder节点。它是数字0的Binder。它作为名称系统,可以为Binder接口分配一个名称。这很重要,因为可能的客户端并不知道远程绑定器地址。如果客户端事先知道远程Binder地址,则Binder的安全令牌功能将不起作用。但是,如果不知道远程伙伴地址,则不会发生初始通信,因为每个Binder接口只知道他自己的地址。上下文管理器解决了这个问题,只有具有固定和先验已知的Binder地址的Binder接口。上下文管理器实现不是Binder框架的一部分。在Android中,上下文管理器的实现称为服务管理器。每个需要发布其名称的Binder是一个服务,向服务管理器提交一个名称和它的Binder标记。依靠这个功能,客户端只需知道服务的名称,并向服务管理器询问所请求的服务的Binder地址。

Intent

意图是一个消息,开发人员使用Java API层,并与Binder IPC一起发送。 “这是一个要执行的操作的抽象表示。”,“抽象”意味着所需操作的执行者不必被定义在意图中。意图作为主要信息包含一个动作和一个数据字段。下图给出了一个意图的例子。该意图由意图参考监视器传送给分配给动作动作拨号的Binder,例如,电话应用程序。这项服务将拨打号码,这是存储在联系人应用程序下的名字。

有两种形式的意图。明确的意图是针对特定的组件。另一方面,一个隐含的意图决定了Android系统,哪个组件被解决。如果安装了一个用途的多个组件,系统将选择最佳组件来运行该意图。

系统集成

Binder广泛用于android平台。每当两个过程必须进行交流,Binder就会参与其中。例如,窗口管理者通过Binder框架与他的客户交换数据。它还使用Binder死亡通知功能在客户端应用程序终止时获得通知。
所以Binder连接Android操作系统的分布式架构,这是一个非常重要的模块。

安全模型

Binder安全模型是非常基本但有效的。它确保了两个进程通信的安全通道,并通过传递PID号和UID号等信息来保证通信伙伴的身份。

意图的另一个特征是意图过滤器。这是一个服务或应用程序的声明,意图由系统转发到这个服务或应用程序。但是它并不能保证所有意图的安全性,因为意图过滤器可以被明确的意图绕过。在最后的结果中,安全性依赖于如上所述的对PID和UID的检查。

Binder框架实现

本章概述了活页夹框架的实现。对于每一层,列出了源代码文件,并讨论了其目的。此外,还介绍了AIDL,这是一个实现功能,因为它生成Java代码,因此可以将其视为Binder框架的一部分。

下图展示了Binder框架的不同层次。它由三层组成。第一层和最高层是Android应用程序的API。第二层是保持Binder框架的用户空间实现的中间件。第三层也是最低层是内核驱动程序。

AIDL

Android interface definition language(Android接口定义语言)是SDK的一部分,有Google提供。他的主要目的是更容易的实现Android远程服务。AIDL遵循类Java语法。

在AIDL文件中,开发人员使用远程服务的方法签名来定义一个接口。 AIDL解析器从接口生成一个Java类,可以用于两个不同的目的。首先,它生成一个代理类,使客户端可以访问服务;其次,它生成一个存根类,服务实现可以使用这个存根类,通过实现远程方法将其扩展为匿名类。

AIDL语言只支持基本的数据类型。它生成的代码负责将值写入包裹,通过Binder IPC发送,接收它们,读取值并调用服务方法和写入并将结果返回。


AIDL文件必须在远程服务应用程序开发者和客户端应用程序开发者之间共享。因为AIDL生成器为客户端和远程服务生成的源代码在一个文件中,因此每个应用程序仅使用和实例化生成的类的一个子集。

Java API包装

本节讨论在中间件和内核驱动程序之上运行的Java框架。这些源类和接口属于Java API层:

Interfaces:
* android.app.IActivityManager
* android.os.Parcable
* android.os.IBinder
* android.content.ServiceConnection

Classes:
* android.app.ActivityMangerNative
* android.app.ContextImpl
* android.content.Intent
* android.content.ComponentName
* android.os.Parcel
* android.os.Bundle
* android.os.Binder
* android.os.BinderProxy
* com.android.internal.os.BinderInternal

Binder框架的Java层有两个功能。一个功能是封装下面的中间件层,让Android应用程序参与Binder通信。作为第二个功能,它向Binder框架引入了新特性,即意图的使用。下图展示了Java层的主要类及其依赖。

JNI包装

Java API层依赖于Binder中间件。要使用Java编写的C ++中间件,必须使用JNI。在源代码文件frameworks/base/core/jni/android_util_Binder.cpp中,实现了Java和C++之间的映射功能。

C++ 中间件

中间件实现了Binder框架的用户空间设施,用C ++编写。该框架提供了产生和管理处理请求的新线程所需的进程和线程控制方法和结构。在这里实现对象编码和解码功能,以便将对象信息转换为可提交的数据包。中间件提供与Binder内核驱动程序的交互并实现共享内存。

以本地C++编写的服务或应用程序可以直接使用Binder框架,但必须放弃在Java API层中实现的功能。
源码包含下列文件:
* frameworks/native/include/binder/IInterface.h
* frameworks/native/include/binder/Binder.h
* frameworks/native/include/binder/BpBinder.h
* frameworks/native/include/binder/IBinder.h
* frameworks/native/include/binder/Parcel.h
* frameworks/native/include/binder/IPCThreadState.h
* frameworks/native/include/binder/ProcessState.h
* frameworks/native/libs/binder/Binder.cpp
* frameworks/native/libs/binder/BpBinder.cpp
* frameworks/native/libs/binder/IInterface.cpp
* frameworks/native/libs/binder/ProcessState.cpp
* frameworks/native/libs/binder/IPCThreadState.cpp

C 核心驱动

Binder内核驱动程序是Binder框架的核心。在这一点上,必须保证消息的可靠和安全传送。内核驱动程序是一个小的内核模块,用C编写。驱动程序模块是从以下源文件构建的:
* frameworks/native/cmds/servicemanager/binder.c
* frameworks/native/cmds/servicemanager/binder.h

Binder内核驱动程序支持文件操作open,mmap,release,poll和系统调用ioctl。这个操作代表了更高层访问Binder驱动程序的接口。 Binder操作打开并建立与Binder驱动程序的连接,并为其分配Linux文件指针,然后释放操作关闭连接。映射Binder内存需要使用mmap操作。主要操作是系统调用ioctl。更高层通过该操作提交并接收所有信息和消息。ioctl操作需要Binder驱动程序命令代码和数据缓冲区作为参数。Binder驱动命令如下:
BINDER_WRITE_READ Binder_WRITE_READ是最重要的命令,它提交了一系列数据,该数据包含多种类型,如下图所示:

BINDER_SET_MAX_THREADS 该命令设置了每个进程处理请求的任务线程的最大数量。

BINDER_THREAD_EXIT 如果Binder线程退出,该命令由中间件发送。

BINDER_VERSION 该命令返回Binder版本号。

Binder驱动的实现将在下部分讨论。这部分使用的命令都是目标命令,即使名字叫做目标应用,但其中一部分会应用到本地Binder中。在OpenBinder文档中,它们被称为Binder驱动程序协议。在后面讨论的有效代码中,Binder命令会以BC_作为前缀,Binder的返回会以BR_作为前缀。

Binder线程支持

由于内核驱动没有实现线程启动机制,因此必须保持最新的启动线程数。这些命令包括BC_REGISTER_LOOPER,BC_ENTER_LOOPER和BC_EXIT_LOOP。发送这些命令,以便Binder驱动程序可以准确计算可用的循环线程数。这些命令是用来记录的并专门为本地Binder设计的。

Binder通讯

使用BC_TRANSACTION和BC_REPLY命令,将数据在两个Binder接口之间传输。BC_REPAY由中间件使用,用来作为收到BC_TRANSACTION命令的应答。下图展示了Binder驱动之间发送数据和等待响应的操作流程。
Binder驱动负责将响应数据回传给等待线程,也可以将回复看做直接返回。Binder驱动将传输数据从发送进程的用户空间复制到它的内核空间,然后再复制这些数据到目的进程的用户空间。这个过程由Linux内核的copy_from_user和copy_to_user命令实现,下图展示了数据传输过程。

更多机制

Binder内核命令BC_INCREFS、BC_RELEASE和BC_DECREFS实现了Binder框架的引用计数特性。“死亡链接”和“死亡通知”特性也是由内核驱动实现。内核驱动管理和持有所有信息,所以必须管理和发送一个Binder节点的终止,这些命令是BC_REQUEST_DEATH_NOTIFICATION,BC_CLEAR_DEATH_NOTIFICATION,BC_DEAD_BINDER_DONE和他们的响应码。

IPC消息流转示例

测试环境

新建一个AIDL文件,提供加法计算服务。

//ISimpleMathService.aidl
interface ISimpleMathService {

    long add(int a, int b);

}

编译后生成java文件结构如下

//ISimpleMathService.java
public interface ISimpleMathService extends android.os.IInterface {

    //IPC服务进程实现存根类
    public static abstract class Stub extends android.os.Binder implements 
            com.zzc.androidtrain.ISimpleMathService { 

            ...
            //省略部分代码
            ...

            //IPC客户端进程调用代理类
            private static class Proxy implements 
            com.zzc.androidtrain.ISimpleMathService { 



            }

    }

    //业务方法声明
    public long add(int a, int b) throws android.os.RemoteException;
}

涉及到三个Java层的Binder框架类:
IInterface.java、Binder.java、IBinder.java

//android.os.IInterface

public interface IInterface
{
    public IBinder asBinder();
}

IInterface接口提供获取Binder对象的能力,避免使用简单的类型转换。
使用场景:存根类和代理类

//android.os.IBinder
public interface IBinder {

    public interface DeathRecipient {
        public void binderDied();
    }

    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException;

    public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException;

    public boolean unlinkToDeath(DeathRecipient recipient, int flags);
}

1、IBinder接口是远程对象的基本接口,是轻量级远程过程调用机制的核心部分,被设计成可以高性能的执行进程内或跨进程调用。这个接口描述了与远程对象进行交互的抽象协议。
2、不要直接实现这个接口,而是从Binder扩展。
3、RPC调用过程是同步的,即:调用一个远程方法后会堵塞等待RPC调用返回。这里保证了本地调用和远程调用的一致性。
4、Binder的客户端进程和服务进程之间通过Parcel传输数据。
5、每个实现了Binder机制的进程中会存在一个交互线程池。
6、Binder跨进程调用同样支持递归调用(像本地调用一样)。
7、Binder的每个跨进程调用都会对应一个唯一的表示,在通过adil生成Binder代码时,编号会自动在FIRST_CALL_TRANSACTION(1)开始递加,但最大值不能超过LAST_CALL_TRANSACTION(16777215)。

//android.os.Binder
public class Binder implements IBinder {

    protected boolean onTransact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {

    }
    ...
    //省略部分代码
    ...

    //Binder代理类
    final class BinderProxy implements IBinder {

        public boolean transact(int code, Parcel data, 
            Parcel reply, int flags) throws RemoteException {
            return transactNative(code, data, reply, flags);
        }

        public native boolean transactNative(int code,  
            Parcel data,  Parcel reply, int flags) throws RemoteException;

    }

}

该类是IBinder接口的标准本地实现,只是一个基本的IPC原语。大多数开发人员不会直接实现这个类,而是使用aidl工具来描述所需的接口,让它生成适当的Binder子类。

消息流转和调用栈

服务端的业务逻辑一般由一个衍生自SimpleMathService.Stub的内部类实现,如下所示


public class RemoteService extends Service{
    private MathBinder myBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        myBinder = new MathBinder();
    }

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

    //业务实现内部类
    private class MathBinder extends ISimpleMathService.Stub {

        @Override
        public long add(int a, int b) throws RemoteException {
            return 0;
        }
    }
}

客户端需要绑定远程服务

ISimpleMathService mService ;
private ServiceConnection mConn = new ServiceConnection () {
    @Override
    public void onServiceConnected (ComponentName name , IBinder service) {
        mService = ISimpleMathService.Stub.asInterface ( service ) ;
    }
    @Override
    public void onServiceDisconnected (
        ComponentName mService = null 
    }
};

当客户端调用bindService方法绑定远程服务时,会根据远程IBinder对象创建一个代理对象ISimpleMathService.Stub.Proxy,该对象与服务端的存根进行通信。如下图所示

//代理方法
@Override
public long add(int a, int b) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    long _result;
      try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeInt(a);
          _data.writeInt(b);
          mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
          _reply.readException();
          _result = _reply.readLong();
      } finally {
          _reply.recycle();
          _data.recycle();
      }
      return _result;
  }

当客户端调用add(1,2)方法时,背后执行流程为:此次调用被代理对象拆分为基础数据类型,只有aidl支持的基础数据类型才可以被写入parcel中。
首先新建两个Parcel对象,用来存放请求数据和响应数据,
然后将Binder接口描述符、参数分别写入请求Parcel中,
然后调用远程Binder对象的transact方法,参数分别是:调用标识(调用哪个方法)、请求参数、响应数据、是否需要返回的flag(0需要,1不需要)。
然后从响应Parcel中读取返回结果。

目前为止,进程间通信已经通过transact方法完成客户端的调用。封装了请求数据的Parcel会通过JNI接口发送到Binder C++中间件,再通过中间件发送到Binder核心驱动。

Binder核心驱动会使客户端进程睡眠,然后将Parcel数据和代码从客户端进程映射到服务端进程。Parcel数据经由Binder驱动 -> C++中间件 -> JNI -> Java API封装层,到达onTransact方法。

//存根方法
@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_add: {
          data.enforceInterface(DESCRIPTOR);
          int _arg0;
          _arg0 = data.readInt();
          int _arg1;
          _arg1 = data.readInt();
          long _result = this.add(_arg0, _arg1);
          reply.writeNoException();
          reply.writeLong(_result);
          return true;
      }
  }
  return super.onTransact(code, data, reply, flags);
}

在onTransact方法中,会根据code(方法调用标识)进入到不同的分支,然后从data(请求数据)中读取参数。
然后调用业务实现方法this.add(arg0, arg1),执行业务处理。
最后将处理结果写入到reply(响应数据)中,并返回ture,表示处理完成。

然后又一次的,通过Binder驱动层,唤醒客户端进程,将响应数据parcel返回给代理对象。

讨论

为了保证进程间通信的安全,避免非法访问(例只允许相同签名的进程可以访问本服务),Binder框架提供了调用进程的UID和PID。使用UID可以检查调用者的软件包签名并识别应用程序。同时客户端进程也可以检查服务端Binder的身份。UID和PID是从Linux系统派生的,可以认为是安全的,不能被任意调用和修改。

猜你喜欢

转载自blog.csdn.net/joye123/article/details/79020543