android 跨进程通信选择的事Binder
关键词联想:管道 消息队列,共享内存,Socket ,可靠性 ,传输性能,安全性
- Binder为什么能跨进程通信?
- android为什么选择binder而不是Socket
再开始本文之前,这两个问题你可以先思考下,这两个问题在Binder及其重要
前言
想想如果在同一进程,你想访问对方内部的函数变量是很简单的。
但是一旦不在一个进程,如Application1进程去访问AMS进程的东西,你就无法"直接"访问,你需要一个东西帮你转接一下。
1、android为什么选择binder而不是Socket
Linux支持的IPC:
- 传统的管道
- System V IPC(消息队列/共享内存/信号量)
- Socket
1.1、可靠性:
Socket需要在各个服务端架设一套协议来实现Client-Server通信但这样增加了系统的复杂性,在手机这种条件复杂,资源稀缺的环境下可靠性也难以保证。
1.2、传输性能:
Socket:
传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信(IM应用)
而消息队列和管道:
采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,
至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。
IPC | 数据拷贝次数 |
---|---|
Socket/管道/消息队列 | 2 |
共享内存 | 0 |
Binder | 1 |
安全性
传统IPC没有任何,安全措施,完全依赖上层协议来确保
主要是传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份
基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder
总结:
看来无论是可靠性还是传输性能,安全性binder是完胜
Binder介绍
Binder机制的模块
Binder驱动 -路由器
Service Manager -DNS
Binder Client-客户端
Binder Service-服务端
正常流程呢 客户端->路由器->dns->路由器->服务端
运输载体 Parcel###
Parcel具有打包和重组能力,为什么会牵扯到打包呢
因为不同进程传递的不再是引用而是原始数据,你给他引用,它帮你把引用指向的数据都打包起来运输过去了。
打包的数据类型没有啥限制,基本数据类型,实体类,数组,都可以,但是有的数据类型还是需要序列化的。
写个例子来玩玩AIDL
首先创建两个工程aidlclient(客户端),aidlserve(服务端)
0.服务端创建AIDL(IMyAidlInterface.aidl)
package com.feitianzhu.aidlserve;
interface IMyAidlInterface {
int add(int num1,int num2);
}
1.建立一个远程服务(IRemoteService)
public class IRemoteService extends Service {
/**
* 将接口暴露给客户端
* @param intent
* @return
*/
@Nullable @Override public IBinder onBind(Intent intent) {
return iBinder;
}
private IBinder iBinder = new IMyAidlInterface.Stub() {
@Override public int add(int num1, int num2) throws RemoteException {
return num1 + num2;
}
};
}
看到onBind方法中,将接口暴露给给客户端,同时你会发现IBinder,
IBinder是啥?
他说接口,Binder是他的实现类
3.注册服务,并在activity中启动
<service android:name=".IRemoteService"
android:process=":remote"
android:exported="true">
</service>
startService(new Intent(this,IRemoteService.class));
ok,运行服务端到手机中,你就服务起来了,接着我们开始客户端跨进程与服务通信了
4.将服务端的AIDL文件复制到客户端去
5.准绑定服务传值调用方法吧
//绑定服务
private void bindService(){
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.feitianzhu.aidlserve",
"com.feitianzhu.aidlserve.IRemoteService"));
bindService(intent,conn, Context.BIND_AUTO_CREATE);
}
大家应该很好奇第二个参数是干什么?
连接后回调,断开后回调,总结就是回调
//绑定服务回调
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//服务绑定成功后调用,获取服务端的接口,这里的service就是服务端onBind返
//回的iBinder即已实现的接口
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//解除绑定时调用,清空接口,防止内容溢出
iMyAidlInterface = null;
}
};
顺利拿到IMyAidlInterface,有了他你就可以通信了,里面有我们在服务端写好的add方法,
我们调用下
mTotal = iMyAidlInterface.add(mNum1,mNum2);
顺利调用add方法得到想要的值
回想上面的代码思路我重新给你们说说这四个组成部门
Server进程:
提供服务的进程
Client进程:
使用服务的进程
ServiceManager进程:
其实它存在感比较低,带有一个Manager,也就是他是服务管理者
这不是我猜的,是事实,他还管理者很多服务
还记得他有DNS的称号么,当你告诉他你要哪个服务的时候,他会给你一个服务代表引用,如上文中的bindservice()
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
Binder驱动:
驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
如果还想深入了解的话,请往下看…其实写到这里大家应该会用AIDL了,但是binder机制大家还是不明白,是吧
如果是的话。。。
我们聊点干活,请往下看
0.Binder在Service服务中的作用
Server注册服务。Server作为众多Service的拥有者,当它想向Client提供服务时,得先去Service Manager(以后缩写成SM)那儿注册自己的服务。Server可以向SM注册一个或多个服务。
Client申请服务。Client作为Service的使用者,当它想使用服务时,得向SM申请自己所需要的服务。Client可以申请一个或多个服务。
当Client申请服务成功后,Client就可以使用服务了。
SM一方面管理Server所提供的服务,同时又响应Client的请求并为之分配相应的服务。扮演的角色相当于月老,两边牵线。这种通信方式的好处是: 一方面,service和Client请求便于管理,另一方面在应用程序开发时,只需为Client建立到Server的连接,就可花很少时间和精力去实 现Server相应功能。
那么,Binder与这个通信模式有什么关系呢?!其实,3者的通信方式就是Binder机制(例如:Server向SM注册服务,使用Binder通信;Client申请请求,用的是Binder通讯)
1.Binder通信机制流程
上图即是Binder的通信模型。我们可以发现:
SM(serviceManager)
Client和Server是存在于用户空间
Client与Server通信的实现,是由Binder驱动在内核空间实现
SM作为守护进程,处理客户端请求,管理所有服务项。
为了方便理解,我们可以把SM理解成DNS服务器; 那么Binder Driver 就相当于路由的功能。
##2.Server向SM注册服务
首先,XXXServer(XXX代表某个)在自己的进程中向Binder驱动申请创建一个XXXService的Binder的实体,
Binder驱动为这个XXXService创建位于内核中的Binder实体节点以及Binder的引用,注意,是将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个名叫XXX的Service。
SM收到数据包后,从中取出XXXService名字和引用,填入一张查找表中。
此时,如果有Client向SM发送申请服务XXXService的请求,那么SM就可以在查找表中找到该Service的Binder引用,并把Binder引用(XXXBpBinder)返回给Client。
在进一步了解Binder通信机制之前,我们先弄清几个概念。
引用和实体。这里,对于一个用于通信的实体(可以理解成具有真实空间的Object),可以有多个该实体的引用(没有真实空间,可以理解成实体的 一个链接,操作引用就会操作对应链接上的实体)。如果一个进程持有某个实体,其他进程也想操作该实体,最高效的做法是去获得该实体的引用,再去操作这个引 用。
有些资料把实体称为本地对象,引用成为远程对象。可以这么理解:引用是从本地进程发送给其他进程来操作实体之用,所以有本地和远程对象之名。
总结:前文中说服务创建,需要去服务总管(serviceManager)那里去注册下,通过太监(binder驱动)去把东西传递给总管(名字和对象引用),服务总管就在簿册中记录下,如果有人要来拿东西了,就把引用给他,但是实体东西始终是在共享空间中,使用者操纵引用就像游戏中你只是操纵游戏中任务,游戏给你反馈,但是你并不是游戏中的任务
3.如何获得SM的远程接口
如果你足够细心,会发现这里有一个问题:
Sm和Server都是进程,Server向SM注册Binder需要进程间通信,当前实现的是进程间通信却又用到进程间通信。这就好比鸡生蛋、蛋生鸡,但至少得先有其中之一。
巧妙的Binder解决思路:
针对Binder的通信机制,Server端拥有的是Binder的实体;Client端拥有的是Binder的引用。
如果把SM看作Server端,让它在Binder驱动一运行起来时就有自己的Binder实体(代码中设置ServiceManager的Binder 其handle值恒为0)。这个Binder实体没有名字也不需要注册,所有的client都认为handle值为0的binder引用是用来与SM通信 的(代码中是这么实现的),那么这个问题就解决了。那么,Client和Server中这么达成协议了(handle值为0的引用是专门与SM通信之用 的),还不行,还需要让SM有handle值为0的实体才算大功告成。怎么实现的呢?!当一个进程调用Binder驱动时,使用 BINDER_SET_CONTEXT_MGR命令(在驱动的binder_ioctl中)将自己注册成SM时,Binder驱动会自动为它创建 Binder实体。这个Binder的引用对所有的Client都为0。
总结:SM在binder驱动一启动的时候就拥有了自己的实体,而它专门用来与SM通信用的
4.Client从SM获得Service的远程接口
Server向SM注册了Binder实体及其名字后,Client就可以通过Service的名字在SM的查找表中获得该Binder的引用了 (BpBinder)。Client也利用保留的handle值为0的引用向SM请求访问某个Service:我申请访问XXXService的引用。 SM就会从请求数据包中获得XXXService的名字,在查找表中找到该名字对应的条目,取出Binder的引用打包回复给client。之 后,Client就可以利用XXXService的引用使用XXXService的服务了。
如果有更多的Client请求该Service,系统中就会有更多的Client获得这个引用。
5.建立C/S通路后
首先要理清一个概念:client拥有自己Binder的实体,以及Server的Binder的引用;Server拥有自己Binder的实体,以及Client的Binder的引用。我们也可以从接收方和发送方的方式来理解:
从client向Server发数据:Client为发送方,拥有Binder的实体;Server为接收方,拥有Binder的引用
从server向client发数据:Server为发送方,拥有Binder的实体;client为接收方,拥有Binder的引用。
也就是说,我们在建立了C/S通路后,无需考虑谁是Client谁是Server,只要理清谁是发送方谁是接收方,就能知道Binder的实体和引用在哪边。
建立CS通路后的流程:(当接收方获得Binder的实体,发送方获得Binder的引用后)
发送方会通过Binder实体请求发送操作。
Binder驱动会处理这个操作请求,把发送方的数据放入写缓存(binder_write_read.write_buffer) (对于接收方为读缓冲区),并把read_size(接收方读数据)置为数据大小(对于具体的实现后面会介绍);
接收方之前一直在阻塞状态中,当写缓存中有数据,则会读取数据,执行命令操作
接收方执行完后,会把返回结果同样用binder_transaction_data结构体封装,写入写缓冲区(对于发送方,为读缓冲区)