在前面的博客中,我们已经学会了使用AIDL进行跨进程通信,AIDL的使用比较简单,可实际上跨进程通信是一个相当复杂的过程,例如进程A是怎么找到进程B的,如果有一个进程C冒充进程A,进程B又该如何识别等等问题,而使用AIDL时,完全不用关心这复杂的过程,开发者只需关注业务逻辑即可,有句话说,哪有什么岁月静好,只不过有人替我们负重前行,AIDL的背后肯定有机制帮我们完成了这些进程间通讯的复杂操作,这个机制便是Binder。
Binder中文意思是黏合剂,这个名称很贴切,生活中的黏合剂是将两种材料通过界面的粘附和内聚强度连接在一起的物质,而Android系统里,Binder则是将两个进程间黏合一起实现通讯。
今天就来了解一下Binder。
2.Binder发展历史Binder是Android系统里进程间通信机制,源于OpenBinder,OpenBinder进程间通信机制最早由Be公司提出,后来这个机制被Palm操作系统所采用,第一个版本也是在Palm的微内核系统上实现,后来Palm改用Linux操作系统,OpenBinder也跟着移植到Linux并且开源,而Android系统基于Linux加上Google聘请了OpenBinder关键工程师Dianne Hackborn加入Android团队,因此OpenBinder自然而然并入Android系统,最初版本的Android时按原样使用OpenBinder,随后改名成Binder并完全重写,成了今天Binder的样子,值得一提的是,OpenBinder已经不再维护,算是寿终正寝,但其机制被Binder所继承,从某种意义上来讲,OpenBinder并未消亡,而是以Binder的形态继续传承下去。
3.为什么使用BinderAndroid系统基于Linux,而Linux系统本身具有多种进程间通信方式可供选择,例如文件,Socket,Pipes,消息队列等,为何陪嫁过来的Binder能够后来居上,成为Android系统里进程间通信的首选呢?
IPC要考虑的因素包括易用,安全,性能,Linux提供的原始IPC选项,易用性好的性能较差,例如Socket作为Client-Service通信方式,开发者使用较方便,但效率低,用于设备间通信较合适,用于进程间通信则臃肿了;性能高的易用性和安全性较差,性能越高,往往越处于底层,底层的接口往往需要开发者考虑许多因素,例如在进程终止时需及时释放资源,否则容易发生内存泄漏,而Binder则很好平衡了这三个因素:
Binder通过共享内存实现高性能,Linux原生的消息队列和管道需数据先从发送方拷贝到内存开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程,而Binder通过共享内存,只需要拷贝一次; Binder通过UID/PID管理发送者和接收者(通过UID / PID),提升了安全性,而Liunx传统IPC没有安全错误,依赖开发者自己控制; 使用简单的AIDL开发,保证了易用性,对开发者友好Binder还有其他好处,包括原生支持多种数据类型,自定义类型使用过程也不复杂,支持同步和异步,自动回收资源等等。
Binder也因为优秀的特性,已深深的和Android融合在一起,正如Hackborn自己所说的,在Android系统上,Binder几乎用于所有跨进程的操作。如果关掉Binder,整个系统将会是没显示,没声音,没输入,处于几乎无法运作的状态,Binder对Android系统的重要性可见一斑。
3.Binder工作机制一个客户端即进程A想和一个服务端即进程B交互,由于进程间的隔离,客户端无法直接调用服务端,但内核可以,因此他们使用Binder驱动捎话,传递消息。用日常生活的例子来说,进程A是一个有多余储蓄,想投资赚点收益投资者,进程B是一个急着用钱的借款人,两人并不熟悉,由于信息不对称,投资者不会直接借钱给借款人,因此也需要一个金融中介,对应于Binder驱动。
由于Binder驱动在内核层,内核层的接口协议比较繁琐,客户端和服务端并不想了解底层协议,只想专注于业务开发,于是他们使用代理(客户端是Proxy,服务端是Stub)来和Binder驱动打交道,还是用金融中介概念理解,投资人当然可以直接和金融中介例如银行打交道,但越底层的业务办起来越罗嗦,例如银行上班时间和我们一样,我们下班了他们也下班,想办点业务还得请假,去到营业厅还得排队,还需要填各种表格,这时候可以找代理帮忙跑腿,省了很多事。
客户端的代理Proxy和服务端的代理Stub不用开发者自己实现,而是依靠AIDL工具,回忆一下AIDL的使用过程,当我们新建一个AIDL文件时,Android SDK工具根据AIDL的描述,自动生成一个同名的java文件,java文件中就包含了Proxy和Stub,以及与Binder驱动通信的代码。这也是为什么反复强调,当更新AIDL文件时,一定要及时同步工程,因为Android SDK工具可能不及时更新同名java文件。
最后一个问题是,Android系统提供的服务端多种多样,客户端怎么联系指定的服务端,这需要依靠Context Manager,Context Manager提供服务的登记和查询功能,类似于现实生活中的银保监会,金融机构需要在银保监会备案,在Android系统中,使用Service Manager实现Context Manager功能,Service Manager是所有服务中第一个启动的,其启动后,其他服务才能登记注册。
服务的向Context Manager注册和客户端向Context Manager查询服务的过程,其实也是一个跨进程通信过程,那么服务端又怎么知道Context Manager的句柄?难道又要弄一个管理Context Manager的死循环过程吗,这倒是没必要,给予Context Manager一个固定的地址即可。过程见下图。
仍以上篇博客创建的AIDL工程为例子(见Android AIDL使用介绍(2)),在该工程中,创建了AIDL例子,Android Studio自动在对应的
build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\包名
目录下生成对应的同名java文件,路径见图
打开看一下里面的内容,代码一开始就有注释提醒我们,这是自动生成的代码,请勿修改,因为是自动生成的代码,可读性上稍微差一点。
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.pm.service;
public interface ServiceAidlInterface extends android.os.IInterface
{
/** Default implementation for ServiceAidlInterface. */
public static class Default implements com.pm.service.ServiceAidlInterface
{
@Override public java.lang.String ServiceGreet() throws android.os.RemoteException
{
return null;
}
@Override public java.util.List getStudentList() throws android.os.RemoteException
{
return null;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.pm.service.ServiceAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.pm.service.ServiceAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.pm.service.ServiceAidlInterface interface,
* generating a proxy if needed.
*/
public static com.pm.service.ServiceAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.pm.service.ServiceAidlInterface))) {
return ((com.pm.service.ServiceAidlInterface)iin);
}
return new com.pm.service.ServiceAidlInterface.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
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_ServiceGreet:
{
data.enforceInterface(descriptor);
java.lang.String _result = this.ServiceGreet();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_getStudentList:
{
data.enforceInterface(descriptor);
java.util.List _result = this.getStudentList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.pm.service.ServiceAidlInterface
{
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 java.lang.String ServiceGreet() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_ServiceGreet, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().ServiceGreet();
}
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public java.util.List getStudentList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getStudentList, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getStudentList();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.pm.service.Student.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.pm.service.ServiceAidlInterface sDefaultImpl;
}
static final int TRANSACTION_ServiceGreet = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getStudentList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.pm.service.ServiceAidlInterface impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.pm.service.ServiceAidlInterface getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public java.lang.String ServiceGreet() throws android.os.RemoteException;
public java.util.List getStudentList() throws android.os.RemoteException;
}
代码的层次分布见下图
ServiceGreet()
和getStudentList()
;
其次是抽象方法的缺省实现,在AIDL工程中,我们已经在Service里实现了抽象方法,假如没有实现的话,则使用自动生成的缺省实现;
再次是内部类Stub,Stub是Binder的子类,跨进程调用由这一个内部类完成,当客户端和服务端不在同一个进程时,走transaction(交易)过程,在当前语境下,transaction指的是一个进程传递数据给另一个进程,传递的数据按照一定的格式要求组包,transaction的业务逻辑由Stub的内部代理类Proxy来完成;
接下来便是onTransact(int code, Parcel data, Parcel reply, int flag)
,这是处理transaction的具体逻辑,这也是客户端和服务端直接沟通的渠道,当客户端发起请求时,这个方法来处理请求,服务端通过code获取客户端想要访问的目标方法,这个code是方法的编号,代码中已经声明static final int TRANSACTION_ServiceGreet = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getStudentList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
第二个参数data用于获取目标方法所需的参数,注意是Parcel类型,当服务端执行完目标方法后,将返回值写入到reply中;
其他重要接口如asInterface()
用于判断当前进程是服务端进程还是客户端进程,如果是服务端进程则返回Stub对象,否则返回Stub.Proxy对象;DESCRIPTOR
用于表示Binder的唯一标识,使用当前当前Binder的包路径表示;asBinder()
返回当前Binder对象。
4.总结
本文主要分析AIDL背后使用到的进程间通信机制Binder,Binder源自于OpenBinder,后来随作者陪嫁到Android系统,凭借着易用性好,性能高,安全性能好,后来居上,成为Android进程间通信机制,成为Android不可或缺的一部分。
Binder的机制细节其实还有很多,本文只是尝试简化说明,如有错漏,欢迎指正。
参考目录:1.Deep Dive into Android IPC/Binder Framework at Android Builders Summit 2013;
2.Android’s Binder – in depth
3.https://www.cnblogs.com/zc9527/p/5638688.html;
4.https://www.cnblogs.com/itgungnir/p/6640120.html