跨进程通信(IPC)是指,不同进程间不能互相直接访问,那么操作系统是如何做到的呢?
进程隔离是操作系统为了保护进程之间不互相干扰而设计的,避免进程A写入进程B的情况发生,其实现使用了虚拟地址空间,两个进程虚拟地址不同,就可以防止A进程写入数据岛B进程。
也就是说,操作系统的不同进程之间,数据不共享,在每个进程看来,自己都独享了整个系统空间,完全不知道其他进程的存在,因此一个进程想要与另一个进程进行通信,需要某种系统机制才能完成。
Linux Kernel是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。
对于Kernel这么一个高安全级别的东西,显然是不容许其它的应用程序随便调用或访问的,所以需要对Kernel提供一定的保护机制,这个保护机制用来告诉那些应用程序,你只可以访问某些许可的资源,不许可的资源是不能被访问的,于是操作系统就把Kernel和上层的应用程序抽像的隔离开,分别称之为Kernel Space和User Space,即内核空间和用户空间。
虽然从逻辑上抽离出用户空间和内核空间,但是不可避免的是,总有那么一些用户空间需要访问内核的资源:比如应用程序访问文件、网络是很常见的事情,怎么办呢?
用户空间访问内核空间的唯一方式就是系统调用,通过这个统一入口,所有的资源访问都是在内核的控制下执行,以免导致用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。
当一个进程执行系统调用,陷入内核代码中执行时,我们就称进程处于内核态,此时处理器处于特权级最高的内核代码中执行。当进程在执行用户自己的代码时,则称其处于用户态,此时处理器在特权级最低的用户代码中运行。处理器在特权等级高的时候才能执行那些特权CPU指令。
通过系统调用,用户空间可以访问内核空间,那么如果一个用户空间想与另外一个用户空间进行通信怎么办呢?
很自然想到的是让操作系统内核添加支持,传统的Linux通信机制,比如Socket、管道等都是内核支持的,但是Binder并不是Linux内核的一部分,那它是怎么做到访问内核空间的呢?
Linux的动态可加载内核模块机制解决了这个问题:模块是具有独立功能的程序,它可以被单独编译,但不能独立运行,它在运行时被链接到内核作为内核的一部分运行在内核空间。这样,Android系统可以通过添加一个运行在内核空间的内核模块,用户进程之间通过这个模块作为桥梁,就可以完成通信了。
在Android系统中,这个运行在内核空间,负责各个用户进程之间通信的内核模块叫做Binder驱动(驱动就是操作硬件的接口)。
Android使用的Linux系统内核,拥有着非常多的跨进程通信机制,比如管道、Socket等,为什么还需要单独搞一个Binder出来呢?主要有两点,性能和安全。
Binder相对于传统的Socket方式,更加高效
比如Socket通信ip地址是客户端手动填入的,都可以进行伪造,而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性
下面我们来看看Binder是如何实现跨进程通信的。
Android系统中有许多的服务,比如Clipboard剪切板服务、ActivityManagerService服务、Vibrator针对服务等等,这些服务都是独立的进程,通常我们的客户端程序想要使用这些系统服务,会通过ServiceManager获取对应的接口对象进行访问,这时就需要跨进程通信,也就是需要借助Binder了,那Binder的通信流程是怎样的呢?
我们把每一个服务端称为Server端,访问他的客户端称为Client端,存储Server端信息的ServiceManager称为SM,假设Client端想要访问一个叫做Test的Server端,调用其A对象的add方法,其过程如下:
首先,当Server端进程初始化时,要向SM中注册自己的信息,类似于一个map的结构:我叫Test,有一个A对象,其中有一个add方法
Client端想要访问时,先向SM端查询,是否有一个叫Test的Server端,我要请求里面的A对象,此时是有的,并要将"A对象"返回
上面说过,进程间通信时,数据会流经内核控件中的Binder驱动,此时驱动会对要传递的数据做一些手脚:如果是同一个进程要获取对象,则返回对象本身接口(不需要做跨进程通信);如果是不同进程要获取对象,则将返回一个和A对象看似一样的AProxy代理对象,这个代理对象也有add方法,其方法只是包装参数,回调给Binder驱动;此处Android的设计是使用代理模式。
Client端拿到这个和A"一样"的AProxy对象,无需关注其是否为代理对象,照常调用add方法传递参数
如上所述,代理对象AProxy内部的add方法会直接交给Binder驱动处理
Binder驱动拿到数据,并知道AProxy对应的真实Server端其实是Test的A对象(数据流经时Binder驱动会记录每个Server端及其对象的信息),于是乎,调用Test端的A对象的add方法,并将参数传递即可
Test端将add方法的返回结果返回给Binder驱动,Binder驱动再返回给对应的Client端的调用处,即可完成一次跨进程通信
综上我们可以发现:
Binder机制使用代理模式,在Server端的对象是实际对象,其他各个进程端所持有的都是Proxy代理对象,由代理对象和Binder驱动完成数据传递
Proxy和实际对象具有同样的接口来实现数据通信,而不用关注其实现细节,因此让我们觉得Binder对象可以直接进行跨进程调用,其实是个数据中转的过程
初次之外,SM也是一个单独的进程,其与各个Server端的注册通信,也是通过同样的Binder跨进程机制完成的,Binder驱动也会对这个过程动手脚,也就是说,SM存放的各个Server端的对象,也都是Proxy代理对象。
一句话总结Binder机制就是:Client进程持有了Server端对象的代理;代理对象系统Binder驱动完成了跨进程通信。
知道了Binder通信的原理和流程,下面我们来看看java层是如何支持Binder的吧。
Java层提供了IBinder、IInterface两个接口
IBinder:提供了Binder跨进程通信的基础方法,如transact()方法用来传递数据;如queryLocalInterface()方法用来获取实际功能对象
IInterface:提供了Binder通信对象的基本类型,我们Server端所提供的功能对象都要实现该接口,其asBinder()方法可以获取对应的Binder对象,也是可以用来传输Binder对象的类型标识
我们要实现一个Server端的提供服务的对象,首先要实现IBinder接口,对Server端来说,其提供服务的对象都是实际的Binder对象(也叫本地对象)而不是代理对象,而Binder类就是实现了部分Server端本地对象的处理逻辑的类,也就是说Binder类就是Server端本地对象的基类,我们来看看它实现了哪些处理逻辑:
public class Binder implements IBinder {
private IInterface mOwner;
private String mDescriptor;
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
@Override
public IInterface queryLocalInterface(String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
@Override
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
protected boolean onTransact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (code == INTERFACE_TRANSACTION) {
reply.writeString(getInterfaceDescriptor());
return true;
} else if (code == DUMP_TRANSACTION) {
...
return true;
} else if (code == SHELL_COMMAND_TRANSACTION) {
...
return true;
}
return false;
}
}
queryLocalInterface()方法里面判断,如果descriptor,也就是IInterface(服务对象)的类名相同的话,就会返回owner,而这个owner和descriptor是服务对象初始化时通过attachInterface()方法传递进来的,也就是说,如果调用服务对象本身的该方法,拿到的就是实际的服务对象本身(本地对象),因为是同一个进程不需要进行跨进程通信
transact()方法,将要传递的数据,包括参数以及要调用的方法(一个int协议值)通过序列化Parcel的方式,传递并调用onTransact()方法
onTransact()方法就是Server端的Binder本地对象要处理的入口方法,其会根据方法的协议int值,决定调用本地对象的哪个方法
Server端的Binder对象有了,下面来看看Client端的Binder对象,上面说过了,Client端拿到的Binder对象是一个代理对象BinderProxy,由Binder驱动生成并返回,我们就来看看这个类是如何进行跨进程通信的。
final class BinderProxy implements IBinder {
@Override
public IInterface queryLocalInterface(String descriptor) {
return null;
}
@Override
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
...
try {
return transactNative(code, data, reply, flags);
} finally {
if (tracingEnabled) {
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
}
}
}
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
}
我们可以看到,BinderProxy也实现了IBinder接口,说明其可以和Binder对象一样进行跨进程通信。
queryLocalInterface()方法,这里由于是本地Binder对象的代理类,所以并不是本地对象,所以这里返回null,需要通过转换为IInterface对象拿到可以调用的接口对象(后面会说)
transact()方法,直接将数据打包,调用一个native方法transactNative(),这个native方法其实就是将数据传入到了Binder驱动,由Binder驱动通过对应信息,调用对应的Server端
以上是java层提供的用于跨进程通信的接口,以及Server端本地Binder对象基类和Client端代理Binder对象的实现,那么我们具体的功能接口如何实现呢?
我们通常定义一组功能,是通过定义一个接口以及里面的一些方法来实现,Android也给我们这样一套功能来定义跨进程通信的功能接口,即AIDL(Android Interface Definition Language)文件,在该文件里,可以像定义普通接口一样定义我们的接口
// TestBinderAIDL.aidl
package com.binder;
// Declare any non-default types here with import statements
import com.binder.TestClientBinderAIDL;
interface TestBinderAIDL {
void test(String text);
void passIBinder(android.os.IBinder iBinder);
void passClientBinder(TestClientBinderAIDL clientBinder);
}
我们可以发现,定义方式和普通接口定义方式基本一致,对于参数类型:
支持基本类型,包括String等
支持IBinder类型,因为IBinder是可以跨进程通信的
其他类型,只有可以被序列化(Parcel),或者可以转换为IBinder的IInterface类型才可,而且需要在头部import进来才可以使用
当定义完AIDL文件,进行编译后,会在AIDL文件所在包名,对应的源代码包中,生成我们想要的功能类
类中包含了Server端Binder类基类Stub、客户端代理类Proxy以及相互转换等实现
public interface TestBinderAIDL extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.binder.TestBinderAIDL {
private static final java.lang.String DESCRIPTOR = "com.binder.TestBinderAIDL";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.binder.TestBinderAIDL interface,
* generating a proxy if needed.
*/
public static com.binder.TestBinderAIDL asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.binder.TestBinderAIDL))) {
return ((com.binder.TestBinderAIDL) iin);
}
return new com.binder.TestBinderAIDL.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_test: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.test(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_passIBinder: {
data.enforceInterface(DESCRIPTOR);
android.os.IBinder _arg0;
_arg0 = data.readStrongBinder();
this.passIBinder(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_passClientBinder: {
data.enforceInterface(DESCRIPTOR);
com.binder.TestClientBinderAIDL _arg0;
_arg0 = com.binder.TestClientBinderAIDL.Stub.asInterface(data.readStrongBinder());
this.passClientBinder(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.binder.TestBinderAIDL {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void test(java.lang.String text) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(text);
mRemote.transact(Stub.TRANSACTION_test, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void passIBinder(android.os.IBinder iBinder) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder(iBinder);
mRemote.transact(Stub.TRANSACTION_passIBinder, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void passClientBinder(com.binder.TestClientBinderAIDL clientBinder) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((clientBinder != null)) ? (clientBinder.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_passClientBinder, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_test = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_passIBinder = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_passClientBinder = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
public void test(java.lang.String text) throws android.os.RemoteException;
public void passIBinder(android.os.IBinder iBinder) throws android.os.RemoteException;
public void passClientBinder(com.binder.TestClientBinderAIDL clientBinder) throws android.os.RemoteException;
}
我们先来看看Server端Binder类基类Stub类干了些什么
首先我们要注意到,我们的功能接口TestBinderAIDL类,实现了IInterface接口,也就是说该接口有了可以跨进程传递的标识,并且可以通过asBinder()方法转换为IBinder对象
public static abstract class Stub extends android.os.Binder implements com.binder.TestBinderAIDL {
private static final java.lang.String DESCRIPTOR = "com.binder.TestBinderAIDL";
static final int TRANSACTION_test = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_passIBinder = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_passClientBinder = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.binder.TestBinderAIDL interface,
* generating a proxy if needed.
*/
public static com.binder.TestBinderAIDL asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.binder.TestBinderAIDL))) {
return ((com.binder.TestBinderAIDL) iin);
}
return new com.binder.TestBinderAIDL.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_test: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.test(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_passIBinder: {
data.enforceInterface(DESCRIPTOR);
android.os.IBinder _arg0;
_arg0 = data.readStrongBinder();
this.passIBinder(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_passClientBinder: {
data.enforceInterface(DESCRIPTOR);
com.binder.TestClientBinderAIDL _arg0;
_arg0 = com.binder.TestClientBinderAIDL.Stub.asInterface(data.readStrongBinder());
this.passClientBinder(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
}
在构造方法时,调用了之前说过的attachInterface()方法,传入的IInterface是this本身,因为Stub本身就是一个IInterface功能接口;传入的descriptor就是我们定义的接口的完整类名。那么这有什么用呢?我们接着往下看
该方法是一个Stub类提供的静态方法,使用过AIDL的人应该都比较熟悉:在我们客户端中,以bindService()为例,会在ServiceConnection的onServiceConnected()方法中拿到Service传递来的IBinder对象,然后需要通过对应AIDL的Stub类的该静态方法转换,才能拿到具体的功能接口类进行调用,比如这里的功能接口就是com.binder.TestBinderAIDL类。
private val conn = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
...
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
remoteBinder = TestBinderAIDL.Stub.asInterface(service)
}
}
再来看asInterface()方法,内部会直接调用IBinder对象queryLocalInterface()方法,传入的descriptor就是接口类名,这时,正如前面所说的,如果这个IBinder对象是Server端的本地Binder对象,那么这里会返回Binder本身,那么asInterface()返回的就是Binder本身,也就是所需要的功能接口;如果IBinder对象是代理对象BinderProxy,那么就会返回null,asInterface()方法返回的就是一个Proxy类对象(下面会讲),而Proxy类也实现了功能接口,所以在客户端看来,无需关注是什么类型,只要知道是功能接口类型,可以调用就行了,这也是代理模式带来的好处!
Stub类由于是Server端的本地Binder对象,所以asBinder()方法返回this自身即可。
该方法是Server端处理调用请求的入口,具体应该调用哪个方法,由协商好的int值来决定,比如这里有三个方法,分别就有三个不同的int值,客户端调用时会根据不同的方法传不同的值,而Server端在该方法里,只需要根据不同值调用不同方法即可,具体的方法实现,我们在Server端里创建一个子类实现接口方法即可,异常简单。
不过这里我们可以发现,数据的传递都是要通过Parcel序列化,包括基本类型、Parcelable类型、IBinder类型、IInterface类型(实际上是通过asBinder()转换为IBinder类型),所以上面说过,我们定义的AIDL接口都要满足这些条件。
下面再来看看Proxy类
首先要注意到,这个类也实现了功能接口(代理模式),也是一个IInterface类型
private static class Proxy implements com.binder.TestBinderAIDL {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void test(java.lang.String text) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(text);
mRemote.transact(Stub.TRANSACTION_test, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void passIBinder(android.os.IBinder iBinder) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder(iBinder);
mRemote.transact(Stub.TRANSACTION_passIBinder, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void passClientBinder(com.binder.TestClientBinderAIDL clientBinder) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((clientBinder != null)) ? (clientBinder.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_passClientBinder, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
因为是代理模式,所以构造Porxy(asInterface()方法中)时,会传入一个真实的IBinder对象作为被代理对象,因此asBinder()方法返回的应该是这个被代理的IBinder对象,毕竟他才是真正的IBinder对象。
Proxy由于是代理类,所以也实现了功能接口相关的方法,而其具体实现,则是调用被代理对象的transact()方法,将参数传入即可,当然除了参数,要传入上述说的方法协议值,指定要调用哪个方法。
Server端的Binder对象不需要跨进程通信,可以直接拿到(同一个进程)进行调用
Client端拿到的Binder对象是BinderProxy对象,通过Proxy类转换为功能接口类型,调用方法最终回调到Server端Binder对象上的指定对象
定义的AIDL接口,参数类型要满足基本类型、可序列化类型或者IBinder(包括IInterface)类型即可
我们平时使用的ClipboardManager、SensorManager、ActivityManagerService等系统服务,其实都是上述这样跨进程通信的