Binder是Android系统进程间通信(IPC)最重要的方式。要想了解Android的系统原理,必须要先对Binder框架有一定的理解。Binder是什么?Binder可以理解为能在进程间进行”通信”的对象,这个通信不是指在不同进程中操作同一个对象,而应理解为一种通信协议。

Binder的引入背景

传统的进程间通信方式有管道,消息队列,共享内存等,其中管道,消息队列采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。Binder通过内存映射的方式,使数据只需要在内存进行一次读写过程。

内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,相反,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<—->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。

Binder的通信模型

主要分为4个部分Binder驱动,Client,Server,ServiceManager(SM),其中Client,Server,ServiceManager(SM)指的是用户空间,并且分别在不同的进程。Binder驱动是内核空间,Binder驱动是一段c语言实现的代码。

服务分系统服务和本地服务,系统服务一般指设备开机时就创建供所有应用使用的服务,如AMS,PMS等,本地服务一般指本地创建的Service,一般供当前应用使用。

由上图可以看出Binder通信的流程是这样的:

  • 首先不同的服务Server一般为service需要先在SM上进行注册
  • Client想获取服务时,先到SM上通过服务名请求服务
  • 得到对应的服务的引用,通过这个引用进行进一步的通信。

由于Client、Server、SM不在同一个进程,所以都需要借助Binder驱动完成通信。这其中有几个关键点。先明确几个概念,Binder Server对象是指真正的进行服务的对象,Biner内核对象是根据Binder Server对象在内核空间的一种表述方式,因为语言层面不同,所以具体的表现形式也不同。比如用应用层表现为Java类或C++类,在内核层表现为结构体了。也可以称为实体,主要是和引用区分开,实体只有一个,而引用可以有很多。

  1. Client、Server、SM不在同一个进程,最初Client和Server怎么得到SM的通信的?
  2. 具体的注册流程是怎样的?
  3. Binder内核对象,BinderProxy对象是同一个对象吗,有什么联系?

首先第一个问题,首先ServiceManage进程本身就是也是采用Binder通信,在系统初始化的时候使用BINDER_SET_CONTEXT_MGR命令将自己注册为ServiceManage,这个过程会在内核空间产生一个ServiceManage的Binder实体。而这个Binder就在内核的0号引用,其他进程通过0号引用找到ServiceManage的Binder实体,通过内存映射就可以和SM建立联系。

第二个问题,首先拥有服务名的Server进程向SM注册时需要借助Binder驱动,此时创建了一个在内核的实体对象,和Server的对象是不同的结构,但拥有Server实体对象的关键信息,比如可以提供的方法等。这个实体对象有一项数据保存Server实体的引用(在内存中可以理解为映射的地址),借助Binder驱动将内核中的实体对像的引用和服务名注册到SM中,这样就有这样一条关系链,SM中服务名—>内核的实体对象的引用—>Server实体对象。

第三个问题,Binder内核对象,BinderProxy对象是同一个对象可定不是同一个对象,因为对应不同的空间,语言层面都有不一样,BinderProxy是系统根据Binder内核对象在Cilent进程新建(new)的一个Binder对象,里面包含Binder内核对象的关键信息,比如所能提供的方法等,这个对象就和真正远程的Server实体对象很相似了,这样Client对象就能像操作Server实体对象一样进行操作,不用考虑Server实在远程还是本地。但具体方法的实现还要通过代理的方式调用远程对象来实现。一般通过transact()和onTransact()来实现代理的调用。

常见的Binder通信过程分析

以startActivity为例。

上图是startActivity过程中的一部分,这里从ContextImpl.startActivity开始分析。

Step1 获得ActivityManagerService在Client的代理

在Instrumenttation中的execStartActivity获得ActivityManagerProxy,过程如下:

1
2
3
4
ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);

ActivityManagerNative.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ActivityManagerProxy(obj);
}

首先通过ServiceManager.getService(“activity”)获取已经注册到
ServiceManager的AMS服务,返回一个IBinder对象,其实这个对象是个BindeProxy对象,BindeProxy在Binder类中可以找到,BinderProxy就是AMS对象在Client中的代理,通过底层C++生成。接着对这个BinderProxy在做一层代理,生成ActivityManagerProxy对象,为什么还要在继续一次代理呢?主要是以因为BindeProxy是一个基本的Binder对象,其并没有实现对数据封装的以及其他的具体逻辑的处理,以及IActivityManager接口的实现。

Step2 Client与远程(系统进程)ActivityManagerService通信——在Client进程

ActivityManagerProxy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
...
data.write(...)
...
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
reply.recycle();
data.recycle();
return result;
}

ActivityManagerProxy实现IActivityManager的接口,这里的mRemote就是上面的BinderProxy对象,通过transact方法进行请求,接下里就是IPC过程到AMS系统进程了。

Step3 系统进程里的ActivityManagerService进行请求处理

ActivityManagerNative.java

ActivityManagerNative是一个抽象类,其实现类就是AMS,通过Binder驱动等一系列IPC过程,上面的

1
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);

最终会调到ActivityManagerNative的onTransact中,ActivityManagerNative通过code值START_ACTIVITY_TRANSACTION来判断具体是哪个请求,因为在不同的进程中方法是没法传递的,所以需要定义一些协议来进行通信,这些协议也就是不同方法对应的code值。

1
2
3
4
5
6
7
8
9
10
onTransact(int code, Parcel data, Parcel reply, int flags) {
switch (code) {
case START_ACTIVITY_TRANSACTION:
...
int result = startActivity(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, options);
reply.writeNoException();
reply.writeInt(result);
return true;
}

ActivityManagerNative是个抽象类,startActivity在在其子类AMS中实现,之后的流程在Android 应用点击图标到Activity界面显示的过程分析

Binder的使用

只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。无论如何,在实现 AIDL 之前,请您务必理解绑定服务。官方文档有详细的讲解

1.利用Binder不使用AIDL实现IPC的过程

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class NoAidlService extends Service {
public static final int TRANSACTION_studyBinder = 0x001;
private static final String DESCRIPTOR = "NoAidlService";
private Binder mNoAidlBinder = new NoAidlBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mNoAidlBinder;
}
private class NoAidlBinder extends Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case TRANSACTION_studyBinder: {
data.enforceInterface(DESCRIPTOR);
String _arg0;
_arg0 = data.readString();
String _result = _arg0+" Study NoAidlService";
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
}
}

注册服务,并指定为远程进程:

1
2
3
<service
android:name=".NoAidlService"
android:process=":remote" />

如果被设置的进程名是以一个冒号”:”开头的,则这个新的进程对于这个应用来说是私有的,当它被需要或者这个服务需要在新进程中运行的时候,这个新进程将会被创建。如果这个进程的名字是以小写字符开头的,则这个服务将运行在一个以这个名字命名的全局的进程中,当然前提是它有相应的权限。这将允许在不同应用中的各种组件可以共享一个进程,从而减少资源的占用。remote这个名称是可以随意起的,关键的是冒号”:”。

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private ServiceConnection mNoAidlConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder clientBinder) {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
String _result;
try {
_data.writeInterfaceToken("NoAidlService");
_data.writeString("SilenceDut");
service.transact(NoAidlService.TRANSACTION_studyBinder, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
mResultTv.setText(mResultTv.getText()+"\n"+_result);
} catch (RemoteException e) {
e.printStackTrace();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};

绑定服务:

1
2
Intent intent = new Intent(this,NoAidlService.class);
bindService(intent, mNoAidlConnection, Context.BIND_AUTO_CREATE);

具体的过程是NoAidlService需要先在Manifest注册信息,这相当于是将服务信息注册到ServiceManage。客户端用Intent将NoAidlService的服务名等信息包装,通过bindService在ServiceManage里寻找NoAidlService,如果成功,则通过onServiceConnected回调得到服务端的信息ComponentName,和服务通信接口clientBinder。

这个clientBinder和NoAidlService通过onBind返回的mNoAidlBinder相似,如果是远程服务,就不是同一个对象,可以理解为是通过Binder驱动得到的一个代理ProxyBinder对象,但看起来和mNoAidlBinder是同一个对象,其实不是的。但如果在一个进程,两个就是同一个对象。

clientBinder通过transact指定服务端对应的函数code比如TRANSACTION_studyBinder,来调用服务端的相应函数,通过Binder驱动,最后会调用到服务端NoAidlBinder的onTransact,通过判断code来确定相应的函数,然后通过再将结果返回,这个过程对Client是阻塞性的,所以客户端调用最好是在异步线程和服务端通信。

对服务端,系统会为每个服务提供线程池,这也容易想到,因为不可能让不同的服务为在调用服务时相互等待。

2.使用AIDL来和Service通信AIDL

AIDL实现IPC通信Demo
AIDL即Android Interface Definition Language,Android接口定义语言。它是一种IDL语言,可以拿来生成用于IPC的代码。

其实就是AIDL文件生成一个帮助类,屏蔽parcel的读写细节,让客户端使用者专注于业务的实现。有一点需要注意的是,如果Service不是在一个新的进程,通过IBinder.queryLocalInterface(DESCRIPTOR)的方式得到本地的Binder对象,也就是和onBind返回的对讲是同一个,就不会涉及到跨进程通信和Binder驱动的调用。这点和不使用AIDL实现IPC是一样的。

参考:【 Android Bander设计与实现 - 设计篇