Android 核心理解 1 :Binder 和 Server
Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块。如下图所示:
(参考《Android内核剖析》 柯元旦 著)
服务端:一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程。该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行到Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务代码。
因此,要实现一个Binder服务,就必须重载onTransact()方法。
其实,重载onTransact()函数的主要内容是把onTransact()函数的参数转换为服务函数的参数,而onTransact()函数的参数来源是客户端调用transact()函数时输入的,因此,如果transact()有固定格式的输入,那么onTransact()就会有固定格式的输出。具体参照客户端transact(...)方法。
比如:基于Binder设计一个Server端
设计Service端很简单,从代码的角度来讲,只要基于Binder类新建一个Servier类即可。以下以设计一个MusicPlayerService类为例。假设该Service仅提供两个方法:start(String filePath)和stop(),那么该类的代码可以如下:
public class MusicPlayService extends Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
// TODO Auto-generated method stub
return super.onTransact(code, data, reply, flags);
}
// other
public void start(String filePath) {
}
public void stop() {
}
}
当要启动该服务时,只需要初始化一个MusicPlayerService对象即可。比如可以在主Activity里面初始化一个MusicPlayerService,然后运行,此时可以在ddms中发现多了一个线程。
Binder驱动:任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。客户端要访问远程服务时,都是通过mRemote对象。
在Binder驱动中,mRemote对象也重载了transact()方法,重载的内容:
- 以线程间消息通信的模式,向服务端发送客户端传递过来的参数。
- 挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知(notify)。
- 接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区。
客户端: 客户端要想访问远程服务,必须获取远程服务在Binder对象中对应的mRemote引用,获得该mRemote对象后,就可以调用其transact()方法。
对应用程序开发员来讲,客户端似乎是直接调用远程服务对应的Binder,而事实上则是通过Binder驱动进行了中转。即存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,所不同的是Binder驱动中的对象不会再额外产生一个线程。
其中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()方法。
IBinder mRemote = null;
String filePath = "/sdcard/music/hello_world.mp3";
int code = 1000;
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
data.writeInterfaceToken("MusicPlayService");
data.writeString(filePath);
try {
mRemote.transact(code, data, replay, 0);
IBinder binder = replay.readStrongBinder();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
replay.recycle();
data.recycle();
首先,包裹不是客户端自己创建的,而是调用Parcel.obtain()申请的。
其中data和reply变量都由客户端提供,reply变量用户服务端把返回的结果放入其中。
writeInterfaceToken() 方法标注远程服务名称,理论上讲,这个名称不是必需的,因为客户端既然已经获取指定远程服务的Binder引用,那么就不会调用到其他远程服务。该名称将作为Binder驱动确保客户端的确想调用指定的服务端。
writeString() 方法用于向Parcel中添加一个String变量。注意,包裹中添加的内容是有序的,这个顺序必须是客户端和服务端事先约定好的,在服务端的onTransact() 方法中会按照约定的顺序取出变量。接着调用transact()方法。调用该方法后,客户端线程进入Binder驱动,Binder驱动就会挂起当前线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹。服务端拿到包裹后,会对包裹进行拆解,然后执行指定的服务函数,执行完毕后,再把执行结果放入客户端提供的reply包裹中。然后服务端向Binder驱动发送一个notify的消息,从而使得客户端线程从Binder驱动代码区返回到客户端代码区。
transact() 的最后一个参数的含义是执行IPC调用的模式,分为两种:一种是双向,用常量0表示,其含义是服务端执行完指定服务后会返回一定的数据;另一种是单向,用常量1表示,其含义是不返回任何数据。
最后,客户端就可以从reply中解析返回的数据了,同样,返回包裹中包含的数据也必须是有序的,而且这个顺序也必须是服务端和客户端事先约定好的。