Binder机制是Android系统中最主要的进程间通信机制。虽然Android底层使用的是linux内核,但是除了匿名管道,socket外,Android并没有使用linux中的命名管道,信号量,消息队列等传统的IPC通信方式。Binder犹如一张大网,将Android整个系统中的组件,跨进程的组织在一起。淡化进程,强化组件是Android的一个设计理念。在这个过程中,Binder发挥了巨大作用。
binder 的作用
binder的作用可以概括为两点:
IPC:是一种跨进程通信手段,但是传输数据的大小受限制,不建议传输较大数据。
RPC:是一种远程过程调用手段,即一个进程中可以透明的调用另外一个进程中的方法。
两者经常相互伴随,例如跨进程调用的时候,参数的传递以及结果的传递等。
binder机制简述
binder实体对象: 就是binder服务的提供者,一个提供binder服务的类必须继承BBinder类(native层),因此binder实体对象又叫做BBinder对象。
binder引用对象: 是binder服务提供者在客户端进程的代表,每个引用对象类型必须继承BpBinder类(native层),因此binder引用对象有叫做BpBinder对象。
binder代理对象: 代理对象简单理解为内聚了一个binder引用对象,因此可以通过这个内聚的引用对象发起RPC调用。可以有多个不同的代理对象,但却内聚了同一个引用对象。
IBinder对象: BBinder和BpBinder都是继承自IBinder.因此binder实体对象和binder引用对象都可以称为IBinder对象。可以通过IBinder.queryLocalInterface()方法来判断到底是binder实体对象还是binder引用对象。
binder跨进程传输的数据类型是Parcel。
以RPC为例的示意图如下:
通过一个运行在内核空间的binder驱动进程,将两个用户空间的进程联系了起来。为解决由于用户空间进程之间虚拟地址相互独立而引起的无法跨进程调用别的进程中的对象方法的难题带来了曙光。
通过BInder引用对象发起RPC调用
假设App中的MainActivity中以startActivityForResult()方法启动了该App中的Main2Activity,那么Main2Activity可以通过AMS这个binder服务中的getCallingActivity()方法查询是谁启动了自己。
AMS.getCallingActivity():
public ComponentName getCallingActivity(IBinder token)
去参数是Activity.mToken. 可以通过反射从客户端Activity组件中获取。
1). 获得AMS的引用binder
AMS作为一个系统服务,在Android系统启动过程中,会将自己注册到ServiceManager中(注册过程以后在分析)。现在只要知道客户端可以通过ServiceManager来获得AMS的引用binder即可。
2). 使用引用binder进行RPC调用时,需要直到要调用的方法的编号,这个编号可以从Android源码中获取。
在Android 6.0 中该方法编号如下:
int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21;
服务端根据这个编号,执行相应的逻辑。
3). 方法参数的传递
binder中唯一能的传递的数据结构就是Parcel,所以必须将方法的参数打包到Parcel中。针对不同的数据类型Parcle提供了不同的方法,来将这些对应的数据打入Parcel中。
另外还要准备一个Parcel用于接收服务端的返回数据。
4). 发起RPC调用
通过引用对象的transact()方法,发起RPC调用,绝大多数情况下,此调用是一个同步调用,也就是说会一直阻塞到服务端将数据返回为止。
但是当transact()方法中传入的flag为FLAG_ONEWAY时,方法会立即返回,不会等到服务端返回数据。
该方法会最终将上述信息传入binder驱动中去。
5). 客户端从返回的Parcel中读取数据
服务端中将请求的方法执行完毕之后,将方法返回值打入到parcel中,binder驱动将其返回到客户端组件中,然后客户端组件按照调用方法的返回值类型,从返回的parcel中读取即可。
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
// 拿到AMS的引用对象
IBinder sm = (IBinder) Reflect.on("android.os.ServiceManager").call("getService","activity").get();
// 拿到方法编号
int funCode = IBinder.FIRST_CALL_TRANSACTION+21;
// 准备方法参数数据
Parcel data = Parcel.obtain();
// 首先要写入的数据必须是binder服务端的descriptor
data.writeInterfaceToken("android.app.IActivityManager");
// 接下来是方法的参数
data.writeStrongBinder((IBinder)Reflect.on(this).field("mToken").get());
// 用于接受返回数据
Parcel reply = Parcel.obtain();
// 发起RPC调用,同步调用,直到调用结束,期间一直阻塞
try {
sm.transact(funCode,data,reply,0);
} catch (RemoteException e) {
e.printStackTrace();
}
// 读取返回数据
reply.readException();
// 解析返回数据
ComponentName res = ComponentName.readFromParcel(reply);
// 回收parcle
data.recycle();
reply.recycle();
Log.i("shajia","calling Activity name: "+res.getClassName());
}
}
通过binder代理对象发起RPC操作
对于前面例子中获取调用者的情况,实际开发中都是通过Activiy.getCallingActivity()来获取的:
public ComponentName getCallingActivity() {
try {
return ActivityManagerNative.getDefault().getCallingActivity(mToken);
} catch (RemoteException e) {
return null;
}
}
其中ActivityManagerNative.getDefault()返回的是ActivityManagerProxy对象。
ActivityManagerProxy是AMS的binder代理类。
binder代理对象通过内聚的binder引用对象间接发起RPC操作。对于系统服务来说,它的binder代理对象都是事先定义好的。binder代理对象还要实现服务接口,实际上就是对binder引用对象发起RPC操作的二次封装。
class ActivityManagerProxy implements IActivityManager
{
...............
public ComponentName getCallingActivity(IBinder token)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
mRemote.transact(GET_CALLING_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
ComponentName res = ComponentName.readFromParcel(reply);
data.recycle();
reply.recycle();
return res;
}
...............
可以看到代理类中已经帮我们封装好了getCallingActivity()的操作。因为直接通过binder引用发起RPC操作的话,需要开发者知道方法的编号,而方法的编号又是随着Android版本的变化而可能发生改变的。所以一般来说都会为binder服务封装一个binder代理类。这样做还有一个好处是通过一个binder引用对象,可以创建多个binder代理对象。
binder服务分类
分为两大类:
向ServiceManager注册的binder服务
Android系统中自带的绝大多数服务,例如AMS,PMS等都会向ServiceManager注册,注册时会传入一个service名字,例如AMS注册是传入的是“activity”。
客户端可以通过名字向ServiceManager查询对象的binder服务,ServiceManager会返回一个IBinder对象。
至于说返回的IBinder对象究竟是实体bidner呢还是引用binder,按照下面的规则决定:
当binder服务端同进程请求该服务时,返回的是binder实体对象。
当请求者与binder服务端在一个进程时,返回的是引用对象。
没有向ServiceManager注册的的bidner服务,又被称为匿名binder服务
典型代表就是App中通过aidl实现的service组件。
aidl实际上帮我们完整实现了服务代理类,以及是是实现了binder服务类中与语义处理相关的所有操作,例如方法编号的分配,方法参数从parcel中的提取,以及返回值打入parcel中等所有的操作。开发者只需要实现服务接口即可。
因为app是没有权限向ServiceManager注册服务的,那么怎么获取app中的额service实体的引用binder呢???
那就是直接传递binder实体,将binder实体对象打入到Parcel中,跨进程传入到AMS中去。
Binder实体在Binder驱动中的传输,会被特殊处理,最终返回到AMS中的是一个binder引用对象。(详细过程后续在分析喽!!!)
其他App进程中的组件便可以通过AMS拿到要请求的service服务的binder引用对象了。要注意的是,此过程中是AMS将所请求的binder服务的引用对象打入Parcel,然后通过Binder驱动传递到请求者进程中。
简单的说就是Binder实体对象的传递过程,伴随着binder服务在Binder驱动中相关数据结构初始化以及binder引用对象的创建过程。
不通过aidl实现一个service,来熟悉一下整个过程:
1. 首先顶定义一个服务接口,即服务要对外提供哪些方法。
IBinderService.java:
public interface IBinderService extends IInterface {
String getMessage();
/**
* 服务的描述,客户端在使用parcel跨进程传输数据的时候
* 必须首先写入服务的描述,即该数据是发给哪个binder的。
* 将来服务端收到数据后会检查这个服务描述是否和自己的一致,不一致就不做处理了
*/
String DESCRIPITON = "MyBinderService";
// 定义方法编号
int GET_MESSAGE = IBinder.FIRST_CALL_TRANSACTION+0;
}
binder服务接口必须继承自IInterface接口,该接口中只有一个方法:
public IBinder asBinder()
binder服务接口一般要包含三部分内容:
首先是该binder服务的描述DESCRIPITON,发送数据时必须先发送该描述,接收数据时必须先对该描述进行检查,和自己不匹配的话那就不用继续进行了。
然后是方法编号,这个编号实际含义要在binder实体对象中的onTransact()方法中才能体现出来。可以理解为onTransact()根据这个编号调用不同的处理分支。
最后就是该服务对外提供的具体方法声明了。
binder实体端和代理对象端必须都继承这个服务接口,并实现其中的方法。另外服务端还要重载binder的onTransact()方法。
2. 实现bidner服务端
binder服务端的重点在于实现onTransact()方法,该方法中会依据客户端传入的方法编号,调用恰当的分支进行处理。
处理过程也是很简单的,就是从parcel中解析参数,调用对应的方法执行,将执行结果打入parcel中。
public class BinderServiceStub extends Binder implements IBinderService {
public BinderServiceStub(){
// 调用该方法后binder实体端的binder.queryLocalInterface()
// 返回就不会为null。
attachInterface(this,DESCRIPITON);
}
// 实现服务接口方法
public String getMessage() {
return " i am from Message!!!!!";
}
/**
* 顾名思义,将自身转换为一个IBinder对象,
* 因为Binder继承子Binder,Binder继承自IBinder
*/
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code){
case GET_MESSAGE:{
// 客户端先发送的是服务描述,所以这里先接收服务描述并判断是否和自己一致
data.enforceInterface(DESCRIPITON);
// 开始执行客户端请求的服务端的方法
String msg = getMessage();
// 将结果打入Parcel
reply.writeNoException();
reply.writeString(msg);
return true;
}
}
/**
* 必须调用父类onTransact处理其他code
*/
return super.onTransact(code, data, reply, flags);
}
}
实现binder代理端
前面介绍了,binder代理对象类会内聚一个binder引用对象,该引用对象通过构造方法传入即可:
public class BinderServiceProxy implements IBinderService {
/**
* 内聚的binder引用对象
*/
private IBinder remote;
public BinderServiceProxy(IBinder binder){
if(binder.queryLocalInterface(DESCRIPITON) == null )
remote = binder;
else
throw new RuntimeException(" this is not a BpBinder.");
}
@Override
public String getMessage() {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(DESCRIPITON);
try {
remote.transact(GET_MESSAGE,data,reply,0);
} catch (RemoteException e) {
e.printStackTrace();
}
reply.readException();
String msg = reply.readString();
data.recycle();
data.recycle();
return msg;
}
@Override
public IBinder asBinder() {
return null;
}
}
这里要特别注意的是,因为binder代理对象类内聚的是一个binder引用对象,所以要对构造方法中传入的Ibinder对象进行检查,保证其是binder引用对象。
然后就是实现服务接口方法,这里很简单了,就是组装Parcel数据,然后利用引用binder对象发起RPC调用。
4. 获取binder引用对象
这就要借助Android中的service组件了,并且以bindService()方法启动该service。
首先定义service组件:
public class MyBinderService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new BinderServiceStub();
}
}
然后在清单文件中,声明该service组件,并且设置process属性,保证该service运行在另外一个进程中:
最后以bindService()方式绑定该service:
Intent intent = new Intent();
intent.setClass(this,MyBinderService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if(service.queryLocalInterface(IBinderService.DESCRIPITON)==null){
// 得到binder代理对象
BinderServiceProxy proxy = new BinderServiceProxy(service);
// 开始执行方法
Log.i("shajia","message: "+proxy.getMessage());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
},BIND_AUTO_CREATE);
binder 安全基础
binder服务端可以通过binder提供的两个api拿到客户端的uid和pid,从而决定是否要对这次请求进行处理:
getCallingUid();
getCallingPid();
binder 死亡通知机制
当端服务进程挂掉的时候,客户端是有必要知道的,可以通过binder引用对象的linkToDeath()方法来设置binder服务死亡监听机制。
如下代码所示:
public BinderServiceProxy(IBinder binder){
if(binder.queryLocalInterface(DESCRIPITON) == null ){
remote = binder;
try {
binder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i("shajia"," binder server is deaded.");
}
},0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
else
throw new RuntimeException(" this is not a BpBinder.");
}
可以在死亡通知处理中做一些资源回收的操作,或者再次重启服务等操作。
到现在为止,已经对binder的表象有了一个大概的了解了,注意只是表象。