Android中的Binder学习笔记
本文内容是我从《Android内核剖析》一书中学习整理。以便以后复习时方便查阅。
一、Binder
1.关于Binder
1.1 Binder是一种架构,这种架构提供了服务器接口、Binder驱动、客户端接口三个模块。
1.2 一个Binder服务器实际上就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程,该线程接下来会接收Binder驱动发送的消息,接收到消息之后,会执行到Binder对象的onTransact()函数,并按照该函数的参数执行不同的服务代码。因此,要实现一个Binder服务,必须重载onTransact()方法。
1.3 任意一个服务端Binder对象呗创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。客户端要访问远程服务时,都是通过mRemote对象。
1.4 Binder框架
1.5 客户端想要访问远程服务,必须获取远程服务在Binder对象中对应的mRemote引用。然后调用该引用的transact()方法,而在Binder驱动中,mRemote对象也重载了transact()方法。
2.设计Service端
2.1 继承Binder类即可设计Service端,如下代码:
代码2-1-1设计Service示例
public class MusicPlayerService extends Binder{
@Override
protected boolean onTransact(int code,Parcel data,Parcel reply,int flags){
switch(code){
case1000:{
data.enforceInterface(“MusicPlayerService”);
String filePath=data.readString();//从包裹中取出一个字符串
start(filePath);
//replay.writeXXX();
break;
}
}
return super.onTransact(code,data,reply,flags);
}
publi cvoid start(String filePath){}
public void stop(){}
}
2.2 当要启动该服务时,只需初始化一个MusicPlayerService对象即可,如果在Activity里面初始化一个MusicPlayerServeice,然后运行,会在DDMS中发现多了一个线程。
2.3 code变量用于表示客户端期望调用服务端的那个函数,因此双方约定一组int值,不同的值代表不同的服务器函数,该值与客户端的transact()函数中第一个参数code的值是一致的。enforceInterface()是为了某种校验,它与客户端的writeInterfaceToken()对应。
3 Binder客户端设计
3.1transact()函数原型为:
public final boolean transact(int code,Parcel data,Parcel reply,int flags);
data表示的是要传递给远程Binder服务的包裹(Parcel),远程服务函数所需要的参数必须放入这个包裹中。包裹中只能放入特定类型的变量如:String、int、long等。除了一般原子变量外,Parcel还提供了writeParcel()方法,因此,要进行Binder远程服务调用时,服务函数的参数要么是一个原子类,要么必须继承Parcel类。参数falsgs含义是执行IPC调用模式,分为两种:一种是双向,用0表示,含义是服务端执行完成后会返回一定的数据;另一种是单向,用1表示,含义是不返回任何数据。
3.2 客户端调用transact()方法:
代码3-3-1客户端调用transact()示例
IBinder mRemote=null;
String filePath=”/sdcard/music/heal_the_world.mp3”;
intcode = 1000;
Parcel data=Parcel.obtain();//包裹不是客户端自己创建的,而是申请到的
data.writeInterfaceToken(“MusicPlayerService”);//标注远程服务名称,不是必需的
data.writeString(filePath);//向包裹中添加String,注意,添加的内容是有序的,约定好的
mRemote.transact(code,data,reply,0);
IBinder binder=reply.readStrongBinder();
reply.recycle();
data.recycle();
3.3 调用transact()方法后,客户端线程进入Binder驱动,Binder驱动会挂起当前线程,并向远程服务发送一个消息,消息中包含客户端传进来的包裹。
4 获取Binder对象
4.1 手工编写Binder服务端和客户端的过程存在两个重要问题
(1)客户端如何获得服务端的Binder对象引用
(2)客户端和服务端必须事先约好两件事情:
(a)服务端函数的参数在包裹中的顺序
(b)服务端不同函数的int型标识
4.2Android的Service与Binder关系:
AmS提供了startService()函数用于启动客户服务,而对于客户端来说,可以使用以下两个函数来和一个服务建立连接,其原型在android.app.ContextImpl类中。
public ComponentName startService(Intent intent);
该函数启动服务后,客户端暂时还没有服务端的Binder引用,因此暂时还不能调用任何服务功能。
public booleanbindService(Intent service,ServiceConnection conn,int flags);
该函数用于绑定一个服务,这就是第一个重要问题的关键所在。第二个参数是一个interface类,该interface的定义如下代码:
代码4-2-1ServiceConnection接口定义
publicinterface ServiceConnection{
public void onServiceConnected(ComponentName name,IBinder service);
public void onServiceDisconnected(ComponentName name);
}
注意该接口中的onServiceConnected()方法的第二个参数service。当客户端请求AmS启动某个Service后,该Service如果正常启动,那么AmS就会远程调用ActivityThread类中的ApplicationThread对象,调用的参数中会包含Service的Binder引用,然后在ApplicationThread中会回调bindService中的conn接口。因此在客户端中可以在onServiceConnected()方法中将其参数Service保存为一个全局变量,从而在客户端的任何地方都可以随时随地调用该远程服务,这就解决了第一个问题,即客户端如何获取远程服务的Binder引用。流程如下:
4.3 保证包裹内参数顺序aidl工具的使用
关于第二个问题,Android的SDK中提供了一个aidl工具,该工具可以吧一个aidl文件转换为一个Java类文件,在该Java类文件,同时重载了transact和onTransact()方法,统一了存入包裹盒读取包裹参数,从而使设计者可以吧注意力放到服务代码本身上。
5.系统服务中的Binder对象
5.1ServiceManager是一个独立进程,其作用是管理各种系统服务,管理的逻辑如图所示:
5.2ServiceManager本身也是一个Service,Framework提供了一个系统函数,可以获取该Service对应的Binder引用,那就是BinderInternal.getContextObject()。该静态函数返回ServiceManager后,就可以通过ServiceManager提供的方法获取其他系统Service的Binder的引用。其他系统服务在启动时,首先把自己的Binder对象传递给ServiceManager,即所谓的注册(addService)。