IPC
我们知道,在操作系统中,进程是最基本的单位,各自拥有独立的内存空间,所以进程间无法直接访问。
所以,各个操作系统都有跨进程通信机制(pipe管道/signal消息/消息队列/共享内存/semaphore信号量/Socket套接字等),IPC就是所有跨进程通信机制的统称。
在Android中,也使用了一些传统的IPC机制,例如Zygote进程的IPC采用的是Socket套接字机制(AMS通过socket通知Zygote为应用fork进程),Android中的Kill Process采用的signal信号机制,但是,在system_server进程和上层App层中,主要使用的是Binder通信机制。
Binder
Binder是Android中最常用的IPC之一,比如每个应用的主线程ActivityThread和AMS系统进程之间,就是使用binder通信(App进程通过ActivityManagerNative引用的ActivityManagerProxy向system server发送bindler消息)- 优势
Android中使用Binder机制,一方面是为了提高效率(数据拷贝少+C和S相对独立不干扰),一方面是为了加强安全性(通信双方身份可以确定,Server可以通过UID/PID判断是否满足访问权限)。 -
原理
Binder是一种C/S结构,Binder通信其实主要做两件事:维护通信地址和建立信息通道,在Binder通信机制里,Service要注册到系统内核的Service Manager里(Service Manager也是一个系统进程),Client从Service Manager里(实际上每个进程都有Service Manager的代理对象)找到要通信的Service,相当于找到通信地址;当进行通信时,通过系统内核提供的Binder驱动,在Client和Service直接建立连接。
Client通过Server代理来访问Server,Server代理会把Client传递的参数打包成Parcel对象,通过mmap映射,实现发送给内核中的Binder驱动,Server端会自己去Binder驱动力读取数据,从中找到发送给自己的Parcel数据,解包处理。
mmap映射过程其实是这样的,跨进程通信需要解决不同用户之间不能共享内存的问题,为此,mmap从接收方的用户空间里映射一块内存出来,放到系统内核,发送方的数据通过copy_from_user()复制到内核空间时,内核会把它放到接收方映射出来的这块儿内存里,所以不需要用copy_to_user()再把数据从内核向接收方复制一遍。
如果要通过Binder传递的数据较大(1M),Binder就会借助匿名共享内存Ashmem(Anonymous Shared Memroy)来传递,基本原理就是对同一块儿物理内存进行映射,在各自进程中映射为各自的虚拟内存,这样就可以在一个进程中写,在另一个进程中读,实现跨进程通信的目的。 - Bind Service
Binder机制在Android中应用非常广泛,它在系统分层上位于Android Framwork层的下一层,所以跨进程通信主要都是通过Binder实现,比如Service和Activity的就可以通过Binder来建立通信,即使它们分处两个进程。基本步骤是这样的:
1.Service提供Binder对象
在Service里自定义一个Binder类
public class YourBinder extends Binder {
YourService getService() { //通过Binder提供你的Service
return YourService.this;
}
}
然后在Service里的onBind函数返回IBinder对象(系统从Server Manager中检索到IBinder对象)
public IBinder onBind(Intent intent) {
return yourBinder;
}
2.Activity发起BindService
Context提供的函数bindService(intent,connection,flag),就是用来向系统申请建立Binder关系的,其中在intent里告诉系统,由谁来提供目标Binder(就是上一步中的Service,Service在onBind中告诉系统Binder)。
而参数connection如下:
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
...
在onServiceConnected回调函数中,IBinder service就是Service返回给系统的那个Binder对象。
Binder是一个C/S结构,这里的Activity就是C的角色,我们可以把多个
C给Bind到同一个S上,如果所有的C都和S解除了绑定(unBindService),并且Server本身不是startService起来的,系统会销毁这个无人需要的Service。
3.双向操作
IBinder service可以直接映射为Service中定义的Binder,然后获取Service实例,这样就能从Activity操作Service。
yourService = ((YourService.YourBinder) service).getService();
获取到Service实例之后,想要从Service反向操作Activity,就可以自己想办法实现了,比如通过接口实现回调,这里就不详述了。
整个过程大概是这样的:
AIDL
开发者经常遇到跨进程通信的需求,如果自己实现Binder机制,工作量就比较大,系统提供了AIDL接口,可以让我们更方便地实现Binder机制,用起来和在Activity里BindService很相似,主要过程如下:
1.新建aidl文件
new一个aidl文件,AS会在你的src/main/java目录同级创建一个src/main/aidl目录,然后在里面创建你的aidl文件及其所属package目录。
2.sync project
sync一下你的工程,AS会建立一个app/build/generated/source/aidl/目录,里面自动生成aidl文件对应的java接口类,这个接口类与你的aidl文件同名。
3.生成Stub
系统生成的aidl接口中,最重要就是Stub抽象类,Stub抽象类扩展android.os.Binder,同时实现你的aidl接口
public static abstract class Stub extends android.os.Binder implements IYourAidlInterface{
...
Stub里做了两件重要的事:作为Binder接收和处理消息
public boolean onTransact(...){...}
以及向调用者提供Binder代理Proxy,以便让对方给自己发消息
private static class Proxy implements IYourAidlInterface {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
...
其实,Binder并不总是返回Proxy跨进程代理,它会判断你们是否在同一进程内,如果在同一进程内,Stub会直接给调用者一个接口对象(因为Stub继承了Binder,又实现了业务接口)。
4.做一个Service
在onBind里返回我们定义的aidlBindler接口实例,实际上是通过Service Manager在本地进程中的代理,去Service Manager中寻找匹配的Service。
5.调用Binder
调用者需要自己实现一个ServiceConnection,通过context.bindService向系统要求绑定Binder,绑定成功后,就可以在ServiceConnection中通过回调函数获取IBinder:
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
yourInterface = IYourAidlInterface.Stub.asInterface(service);
这一步里,其实是Stub视双方进程情况,返回接口或Binder代理Proxy,Stub会通过这段代码检查进程:
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
如果是同一进程内,不需要跨进程,就直接提供一个nterface接口;如果是跨进程,就提供一个Binder对象代理(IYourAidlInterface.Stub.Proxy)来实现跨进程。
6.跨进程传输
在跨进程情况下,系统会使用Binder代理Proxy来传输数据,Proxy里有一个IBinder接口对象。
Proxy传递数据其实就是调用IBinder对象,用IBinder对象的transact函数发出数据,这就会触发服务端的onTransact回调函数(在Stub里实现onTransact),服务端再把计算结果写入_reply返回值,Proxy就能通过_reply拿到返回数据:
//向服务端发送数据
mRemote.transact(Stub.TRANSACTION_getInfor, _data, _reply, 0);
_reply.readException();
//接收服务端返回值
_result = _reply.readString();
所以,客户端主要是通过Stub进行通信,相关过程和关系大概是这样的:
与Intent的对比
Intent是另一种很常用的通信机制,一般会被打包为Parcel进行传输,Intent与Binder相比,效率很低,因为它是发送给系统进程,系统对intent过滤后,找到目标组件,再由系统把数据转交给目标组件,不像Bindler直接在C-S之间通信来得高效。
Intent有7个属性:ComponentName、Action、Category、Data、Type、Extra以及Flag,这7种属性可以分三类。
第一类:启动过滤,有ComponentName(显式),Action(隐式),Category(隐式)。
第二类:启动模式,Flag。
第三类:数据传值,有Data(隐式),Type(隐式),Extra(隐式、显式)。
Intent分显式和隐式两种:
- 显式Intent就是用setClass或setComponent明确指定了启动哪个Activity或其他组件。所以显式Intent效率高,但是耦合度高。
- 隐式Intent就是不直接指定要启动的组件,而是通过设置Intent Filter过滤条件(Action+Category),由系统来筛选优先级最高的那个Activity或其他组件,如果有多个最高优先级的组件,会提示用户手动选择。所以隐式Intent效率低,但是耦合度低。
隐式Intent必须有Action,并且在Category中至少有一个android.intent.category.DEFAULT,否则无法匹配过滤。
在数据传值时,Data其实是传一个URI,如果想为这个URI定义一个TYPE,就需要把URI和TYPE一起传给Intent,例如:intent.setDataAndType(Uri.parse(url), "audio/mp3");。
Intent还可以放Android系统剪切数据,intent.setClipData(ClipData);
与ContentProvider对比
ContentProvider就更弱了,它只是作为数据接口,向其他进程提供数据,我们也可以通过android:multiprocess属性,让每个访问进程都自己创建一个ContentProvider实例,不用去跨内存访问,当然,这样会有内存和数据同步问题,但是数据访问效率高。
ContentProvider的基本原理是ASHMEM匿名共享内存,ContentProvider保存的数据本来是不对其他进程开放的,但是其他内存可以创建一块匿名共享内存,然后用Binder将CursorWindow和共享内存文件描述传递过来,让ContentProvider也指向这块儿共享内存,从而实现跨进程数据访问。
引用
IPC、Binder、AIDL与Intent之间区别与联系
Android Bander设计与实现 - 设计篇
轻松理解 Android Binder,只需要读这一篇
Android 中AIDL的使用与理解
一次搞定Process和Task
Android学习笔记——Activity的启动和创建