参考文章:写给 Android 应用工程师的 Binder 原理剖析

实验代码:TheBinder

Binder机制可以说是Android的核心。提到Binder,可能会让你想到,通过bindService与Service进行通信(也可能是跨进程的通信),实际上,Android中Binder的使用可以说是无处不在的,包括Activity跳转,详情可以参考AMS启动流程

为什么要用Binder?

在Linux系统中,跨进程通信(IPC)方式有很多种,包括Socket、管道、共享内存等。可以Android为何最后选择Binder作为核心的跨进程通信的手段呢?

这需要从两方面去分析——性能安全性

1. 性能

首先说说性能上的优势。Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。Binder 只需要一次数据拷贝,性能上仅次于共享内存。

2. 安全性

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

基于上述原因,Android 需要建立一套新的 IPC 机制来满足系统对稳定性、传输性能和安全性方面的要求,这就是 Binder。

用一张表格来总结与对比各种IPC方式。

IPC方式 性能(内存拷贝次数) 安全性
Binder 1 通过UID/PID来保证
共享内存 0 操作非常复杂,难以保证
Socket/管道/消息队列 2 依靠上层协议做身份识别,非常不可靠

传统IPC是什么样的?

先要了解一些基本概念——进程隔离用户空间内核空间用户态内核态

IPC

上图展示了 Liunx 中跨进程通信涉及到的一些基本概念:

  • 进程隔离
  • 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)
  • 系统调用:用户态/内核态

进程隔离

顾名思义,就是进程之间内存是不共享的。进程间要进行数据交换,就得采用**进程间通信(IPC)**机制。

进程空间划分:用户空间(User Space)/内核空间(Kernel Space)

现在操作系统都是采用的虚拟存储器,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。

简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。

linux memory

系统调用:用户态/内核态

虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。

Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。

当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。

系统调用主要通过如下两个函数来实现:

1
2
copy_from_user() //将数据从用户空间拷贝到内核空间
copy_to_user() //将数据从内核空间拷贝到用户空间

Linux下传统IPC通信

linux ipc

数据发送进程:开辟用户空间缓存区 -> 系统调用,进入内核态 -> 开辟内核空间缓存区 ->通过copy_from_user()将数据拷贝到内核空间缓存区。

数据接收进程:开辟用户空间缓存区 -> 调用copytouser()将数据从内核缓存区拷贝到用户空间缓存区。

这样,两个进程就完成了依次进程间通信。

这种传统的 IPC 通信方式有两个问题:

  1. 性能低下,一次数据传递需要经历:内存缓存区 –> 内核缓存区 –> 内存缓存区,需要 2 次数据拷贝;
  2. 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

Binder跨进程通信原理

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

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

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

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

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

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

Binder IPC实现原理

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

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

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

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

  1. 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
  2. 接着在内核空间开辟一块内核缓存区,建立内核缓存区内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区接收进程用户空间地址的映射关系;
  3. 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

如下图:

binder

注意图中两个红色虚线。我们从图中可以看到,内核空间开辟了两个缓存区——内核缓存区数据接收缓存区,这两个缓存区之间存在内存映射,然后数据接收缓存区数据接收进程的用户空间缓存区同样有内存映射。当数据发送进程通过copy_from_user()将数据拷贝到内核缓存区的时候,存在映射关系的数据接收进程用户空间缓存区也就收到了数据。

Binder通信模型

跨进程通讯至少包含两个进程,我们将数据发送进程称为Client,把数据接收方称为Server

Client/Server/ServiceManager/驱动

前面我们介绍过,Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。

img

Android Binder 设计与实现一文中对 Client、Server、ServiceManager、Binder 驱动有很详细的描述,以下是部分摘录:

Binder 驱动
Binder 驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

ServiceManager 与实名 Binder
ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder,就像网站一样除了除了有 IP 地址意外还有自己的网址。Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。

细心的读者可能会发现,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。

Client 获得实名 Binder 的引用
Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用一样。

Binder通信过程

至此,我们大致能总结出 Binder 通信过程:

  1. 首先,一个进程使用 BINDERSETCONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
  2. Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
  3. Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。

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

img

Binder 通信中的代理模式

我们已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了,但是还有个问题会让我们困惑。A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。

前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。

当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。

img

Binder的完整定义

现在我们可以对 Binder 做个更加全面的定义了:

  • 从进程间通信的角度看,Binder 是一种进程间通信的机制;
  • 从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象;
  • 从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理
  • 从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会对这个跨越进程边界的对象对一点点特殊处理,自动完成代理对象和本地对象之间的转换。

实现Binder跨进程通信

一般Android上,使用**AIDL(Android Interface Definition Language)**来实现跨进程通信协议。AIDL主要是对接口进行描述的,包括定义Server为Client提供那些操作服务。通过AIDL文件,Android Studio在编译时候,会自动产生接口类以及代理类。

除了通过AIDL的方式,我们还可以自己手动编写接口类和代理类。

代码请参考TheBinder,这里展示了两种方式实现Binder IPC。

代码中涉及到了一些Java类。

  • IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。
  • IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)
  • Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。
  • Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

自己实现

代码请参考TheBinder

代码中有两个module: withAIDLnoAIDL,分别演示了使用AIDL的方式和不使用ADIL的方式进行Binder IPC。

NoAIDL相比WithAIDL有一个优点,就是可以在对应的IInterface文件中,添加一些自定义的代码,比如添加log代码;由于AIDL的方式是自动生成的代码,所以这些自定义代码是没法添加到对应的IInterface文件中。

我们重点关注noAIDL

1
2
3
4
5
6
7
8
9
10
11
12
13
//INoAIDL.kt
interface INoAIDL : IInterface {
fun sayHi(name: String)
fun showObjN(objN: ObjN): ObjN?

companion object {
private val TAG = INoAIDL::class.java.simpleName
private val DESCRIPTOR = INoAIDL::class.java.name

abstract class Stub : Binder(), INoAIDL {...}
class Proxy(private val remote: IBinder) : INoAIDL {...}
}
}

INoAIDL.kt文件的大体结构如上代码,我们可以看到,在这个IInterface类中:

  • 定义两个Server端承诺的服务——sayHishowObjN
  • 两个静态类——一个Stub类和一个Proxy类。

Stub类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
abstract class Stub : Binder(), INoAIDL {

companion object {
const val TRANSACTION_sayHi = IBinder.FIRST_CALL_TRANSACTION + 0
const val TRANSACTION_showObjN = IBinder.FIRST_CALL_TRANSACTION + 1

fun asInterface(binder: IBinder?): INoAIDL? {
if (binder == null) {
return null
}
val iin = binder.queryLocalInterface(DESCRIPTOR)
if (iin != null && iin is INoAIDL) {
Log.v(TAG, "Client and Server in the same Process")
return iin
}
Log.v(TAG, "Client and Server in different Processes")
return Proxy(binder)
}
}

init {
attachInterface(this, DESCRIPTOR)
}

final override fun attachInterface(owner: IInterface?, descriptor: String?) {
super.attachInterface(owner, descriptor)
}

override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
val descriptor = DESCRIPTOR
return when(code) {
INTERFACE_TRANSACTION -> {
reply?.writeString(descriptor)
true
}
TRANSACTION_sayHi -> {
data.enforceInterface(descriptor)
val name = data.readString() ?: ""
sayHi(name)
reply?.writeNoException()
true
}
TRANSACTION_showObjN -> {
data.enforceInterface(descriptor)
var objN: ObjN? = null
if (0 != data.readInt()) {
objN = ObjN.CREATOR.createFromParcel(data)
}
val _result = showObjN(objN!!)
reply?.writeNoException()
if (_result != null) {
reply?.writeInt(1)
_result.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE)
} else {
reply?.writeInt(0)
}
true
}
else -> {
super.onTransact(code, data, reply, flags)
}
}
}

override fun asBinder(): IBinder {
return this
}
}

在Manifest文件中我们定义对应Service

1
2
3
4
5
6
7
<service
android:name=".NoAIDLService"
android:enabled="true"
android:exported="true"
/>
<!--android:process=":noAIDL"-->
<!--把上述属性设置到service中,则是在不同进程中运行-->

我们重点关注asInterface方法,在这个方法中,有一个代码片段:

1
2
3
4
5
6
7
val iin = binder.queryLocalInterface(DESCRIPTOR)
if (iin != null && iin is INoAIDL) {
Log.v(TAG, "Client and Server in the same Process")
return iin
}
Log.v(TAG, "Client and Server in different Processes")
return Proxy(binder)

通过这样的代码来进行Client和Server是否在不同进程的判断。

  • 相同进程:返回queryLocalInterface出来的对象,这个对象是在Stub构造方法中通过attachInterface方法传入的。
  • 不同进程:构造一个Proxy类返回。

MainActivity去bind一个Service,Service返回一个Binder。我们打印一下日志。

1
2
3
4
5
6
7
class NoAIDLService : Service() {
override fun onBind(intent: Intent): IBinder {
return binder.also {
Log.v(TAG, "onBind=$it")
}
}
}
1
2
3
4
5
6
7
8
9
10
11
private var noAIDL: INoAIDL? = null
private val noConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
service ?: return
Log.v(TAG, "onServiceConnected service=$service")
noAIDL = INoAIDL.Companion.Stub.asInterface(service)
Toast.makeText(this@MainActivity, "connected to NoAIDLService", Toast.LENGTH_SHORT).show()
Log.v(TAG, "onServiceConnected noAIDL=${noAIDL}")
}
}

当Client与Server在相同进程,有如下日志:

1
2
3
4
5
6
// NoAIDLService
onBind=com.github.boybeak.noaidl.NoAIDLService$binder$1@84abbdc

// MainActivity
onServiceConnected service=com.github.boybeak.noaidl.NoAIDLService$binder$1@84abbdc
noAIDL=com.github.boybeak.noaidl.NoAIDLService$binder$1@84abbdc

当Client与Server在不同进程,有如下日志:

1
2
3
4
5
6
// NoAIDLService
onBind=com.github.boybeak.noaidl.NoAIDLService$binder$1@2d32060

// MainActivity
onServiceConnected service=android.os.BinderProxy@99b42f6
noAIDL=com.github.boybeak.noaidl.INoAIDL$Companion$Proxy@67e8964

我们看到,当Client与Server在相同进程时候,Service的onBind方法返回的是什么,MainActivity接收到的就是什么;而当Client与Server在不同进程的时候,则返回的是Binder驱动传递给我们的对象,通过这个对象,我们创造一个 Proxy代理对象。

我们接下来重点关注showObjN方法。

1
2
3
4
5
6
private val binder = object : INoAIDL.Companion.Stub() {
override fun showObjN(objN: ObjN): ObjN? {
Log.v(TAG, "showObjN objN=$objN")
return objN
}
}

这个方法中,直接返回传入的参数。

调用的地方这样来写:

1
2
3
4
5
6
7
8
9
10
fun showObjN(v: View) {
if (noAIDL == null) {
Toast.makeText(this, "Click NOAIDL button first", Toast.LENGTH_SHORT).show()
return
}
val objN = ObjN()
Log.v(TAG, "showObjN objN=$objN")
val returnObjN = noAIDL?.showObjN(objN)
Log.v(TAG, "showObjN returnObjW=$returnObjN")
}

我们来看打印的objN日志。

相同进程情况下:

1
2
3
4
5
6
// Client进程
com.github.boybeak.binder V/MainActivity: showObjN objN=com.github.boybeak.noaidl.ObjN@4c7d499
com.github.boybeak.binder V/MainActivity: showObjN returnObjW=com.github.boybeak.noaidl.ObjN@4c7d499

// Server进程
com.github.boybeak.binder V/NoAIDLService: showObjN objN=com.github.boybeak.noaidl.ObjN@4c7d499

不同进程情况下:

1
2
3
4
5
6
// Client进程
com.github.boybeak.binder V/MainActivity: showObjN objN=com.github.boybeak.noaidl.ObjN@5ab3a61
com.github.boybeak.binder V/MainActivity: showObjN returnObjW=com.github.boybeak.noaidl.ObjN@db79986

// Server进程
com.github.boybeak.binder V/NoAIDLService: showObjN objN=com.github.boybeak.noaidl.ObjN@1c5381d

我们可以看到,在相同进程情况下,就是普通的函数调用;在不同进程情况下,Client传入的参数,Server接收到的参数,Client接收到的返回结果,全是不同的对象,这是因为,通过Proxy对象,在跨进程通信时候,将Parcelable对象进行了序列化和反序列化。

本文是在阅读写给 Android 应用工程师的 Binder 原理剖析一文后,加上自己的理解与实验完成,其中部分段落直接复制了原文,因为我觉得,那部分原文已经足够容易理解且没有冗余的文字,感谢原作者张磊,同时附上原文参考资料:

本文采用CC-BY-SA-3.0协议,转载请注明出处
Author: boybeak