
第 5 章 Binder
Binder,英文的意思是别针、回形针。我们经常用别针把两张纸“别”在一起,而在 Android 中,
Binder 用于完成进程间通信(IPC),即把多个进程“别”在一起。比如,普通应用程序可以调用音乐播
放服务提供的播放、暂停、停止等功能。
Binder 工作在 Linux 层面,属于一个驱动,只是这个驱动不需要硬件,或者说其操作的硬件是基于
一小段内存。从线程的角度来讲,Binder 驱动代码运行在内核态,客户端程序调用 Binder 是通过系统
调用完成的。
5.1 Binder 框架
Binder 是一种架构,这种架构提供了服务端接口、Binder 驱动、客户端接口三个模块,如图 5-1 所示。
首先来看服务端。一个 Binder 服务端实际上就是一个 Binder 类的对象,该对象一旦创建,内部就
启动一个隐藏线程。该线程接下来会接收 Binder 驱动发送的消息,收到消息后,会执行到 Binder 对象
中的 onTransact()函数,并按照该函数的参数执行不同的服务代码。因此,要实现一个 Binder 服务,就
必须重载 onTransact()方法。
可以想象,重载 onTransact()函数的主要内容是把 onTransact()函数的参数转换为服务函数的参数,
而 onTransact()函数的参数来源是客户端调用 transact()函数时输入的,因此,如果 transact()有固定格式
的输入,那么 onTransact()就会有固定格式的输出。
下面再看 Binder 驱动。任意一个服务端 Binder 对象被创建时,同时会在 Binder 驱动中创建一个
mRemote 对象,该对象的类型也是 Binder 类。客户端要访问远程服务时,都是通过 mRemote 对象。
Be From--http://bmbook.5d6d.com/

第 5 章 Binder
79
图 5-1 Binder 框架
最后来看应用程序客户端。客户端要想访问远程服务,必须获取远程服务在 Binder 对象中对应的
mRemote 引用,至于如何获取,下面几节将要介绍。获得该 mRemote 对象后,就可以调用其 transact()
方法,而在 Binder 驱动中,mRemote 对象也重载了 transact()方法,重载的内容主要包括以下几项。
以线程间消息通信的模式,向服务端发送客户端传递过来的参数。
挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知
(notify)。
接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区。
从这里可以看出,对应用程序开发员来讲,客户端似乎是直接调用远程服务对应的 Binder,而事 实
上则是通过 Binder 驱动进行了中转。即存在两个 Binder 对象,一个是服务端的 Binder 对象,另一个则
是 Binder 驱动中的 Binder 对象,所不同的是 Binder 驱动中的对象不会再额外产生一个线程。
Be From--http://bmbook.5d6d.com/

Android
内核剖析
80
5.2 设计 Servier 端
设计 Service 端很简单,从代码的角度来讲,只要基于 Binder 类新建一个 Servier 类即可。以下以
设计一个 MusicPlayerService 类为例。
假设该 Service 仅提供两个方法:start(String filePath)和 stop(),那么该类的代码可以如下:
当要启动该服务时,只需要初始化一个 MusicPlayerService 对象即可。比如可以在主 Activity 里面
初始化一个 MusicPlayerService,然后运行,此时可以在 ddms 中发现多了一个线程,如图 5-2 所示。
图 5-2 使用 ddms 工具查看 Binder 对应的线程
如果不创建 MusicPlayerService,则只有三个 Binder 对象对应的线程。
定义了服务类后,接下来需要重载 onTrasact()方法,并从 data 变量中读出客户端传递的参数,比如
start()方法所需要的 filePath 变量。然而,这里有个问题,服务端如何知道这个参数在 data 变量中的位
置?因此,这就需要调用者和服务者双方有个约定。
这里假定客户端在传入的包裹 data 中放入的第一个数据就是 filePath 变量,那么,onTransact()的代
码可以如下所示:
Be From--http://bmbook.5d6d.com/

第 5 章 Binder
81
code 变量用于标识客户端期望调用服务端的哪个函数,因此,双方需要约定一组 int 值,不同的值
代表不同的服务端函数,该值和客户端的 transact()函数中第一个参数 code 的值是一致的。这里假定 1000
是双方约定要调用 start()函数的值。
enforceInterface()是为了某种校验,它与客户端的 writeInterfaceToken()对应,具体见下一小节。
readString()用于从包裹中取出一个字符串。取出 filePath 变量后,就可以调用服务端的 start()函数了。
如果该 IPC 调用的客户端期望返回一些结果,则可以在返回包裹 reply 中调用 Parcel 提供的相关函
数写入相应的结果。
5.3 Binder 客户端设计
要想使用服务端,首先要获取服务端在 Binder 驱动中对应的 mRemote 变量的引用,获取的方法后
面小节将有介绍。获得该变量的引用后,就可以调用该变量的 transact()方法。该方法的函数原型如下:
public final boolean transact(int code, Parcel data, Parcel reply,int flags)
其中 data 表示的是要传递给远程 Binder 服务的包裹(Parcel),远程服务函数所需要的参数必须放入
这个包裹中。包裹中只能放入特定类型的变量,这些类型包括常用的原子类型,比如 String、int、long
等,要查看包裹可以放入的全部数据类型,可以参照 Parcel 类。除了一般的原子变量外,Parcel 还提供
了一个 writeParcel()方法,可以在包裹中包含一个小包裹。因此,要进行 Binder 远程服务调用时,服务
函数的参数要么是一个原子类,要么必须继承于 Parcel 类,否则,是不能传递的。
因此,对于 MusicPlayerService 的客户端而言,可以如下调用 transact()方法。
现在来分析以上代码。首先,包裹不是客户端自己创建的,而是调用 Parcel.obtain()申请的,这正
如生活中的邮局一样,用户一般只能用邮局提供的信封(尤其是 EMS)。其中 data 和 reply 变量都由客
户端提供,reply 变量用户服务端把返回的结果放入其中。
writeInterfaceToken()方法标注远程服务名称,理论上讲,这个名称不是必需的,因为客户端既然已
经获取指定远程服务的 Binder 引用,那么就不会调用到其他远程服务。该名称将作为 Binder 驱动确保
客户端的确想调用指定的服务端。
writeString()方法用于向包裹中添加一个 String 变量。注意,包裹中添加的内容是有序的,这个顺
序必须是客户端和服务端事先约定好的,在服务端的 onTransact()方法中会按照约定的顺序取出变量。
Be From--http://bmbook.5d6d.com/
评论2