谈到android进程间通信,就不得不想到Binder,那么他到底是何方圣神呢?话不多说,咱们下面一起来解密一番!
IBinder
首先我们来看一下Binder的声明:
public class Binder implements IBinder {...}
哟,那么IBinder又是什么呢?
public interface IBinder {
int FIRST_CALL_TRANSACTION = 0x00000001;
int LAST_CALL_TRANSACTION = 0x00ffffff;
int PING_TRANSACTION = ('_'<<24)|('P'<<16)|('N'<<8)|'G';
int DUMP_TRANSACTION = ('_'<<24)|('D'<<16)|('M'<<8)|'P';
int SHELL_COMMAND_TRANSACTION = ('_'<<24)|('C'<<16)|('M'<<8)|'D';
int INTERFACE_TRANSACTION = ('_'<<24)|('N'<<16)|('T'<<8)|'F';
int TWEET_TRANSACTION = ('_'<<24)|('T'<<16)|('W'<<8)|'T';
int LIKE_TRANSACTION = ('_'<<24)|('L'<<16)|('I'<<8)|'K';
int SYSPROPS_TRANSACTION = ('_'<<24)|('S'<<16)|('P'<<8)|'R';
int FLAG_ONEWAY = 0x00000001;
public static final int MAX_IPC_SIZE = 64 * 1024;
public @Nullable String getInterfaceDescriptor() throws RemoteException;
public boolean pingBinder();
public boolean isBinderAlive();
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor);
public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException;
public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException;
public void shellCommand(@Nullable FileDescriptor in,
@Nullable FileDescriptor out, @Nullable FileDescriptor err,
@NonNull String[] args, @Nullable ShellCallback shellCallback,
@NonNull ResultReceiver resultReceiver) throws RemoteException;
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException;
public interface DeathRecipient {
public void binderDied();
}
public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException;
public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
}
哦,原来IBinder 是一个定义了跨进程传输能力的接口。只要实现了这个接口,就能将这个对象进行跨进程传递。
这些方法中重点关注 transact(),Binder里面与它对应的是 Binder.onTransact()。
Binder
Binder是官方提供的实现了IBinder接口的操作,它是 Android IPC 的基础,平常接触到的各种 Manager(ActivityManager, ServiceManager 等),以及绑定 Service 时都在使用它进行跨进程操作。
下面介绍几个关键方法:
- transact()
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
连接Binder驱动,发起IPC请求。
- onTransact()
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
if (code == INTERFACE_TRANSACTION) {
......
} else if (code == DUMP_TRANSACTION) {
......
} else if (code == SHELL_COMMAND_TRANSACTION) {
......
}
return false;
}
根据 code 对传入的参数 data 做相应的处理,然后写入 reply,这样就能返回操作后的数据。
- attachInterface()
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
这个方法的作用是将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个 IInterface,那 IInterface 又是什么呢?
public interface IInterface{
public IBinder asBinder();
}
IInterface 里只定义了一个 asBinder() 方法,这个方法可以返回当前接口关联的 Binder 对象。
Binder通信机制
上面只是简单介绍了Binder类相关的信息,具体的详细信息可以去查看源码,但这只是Binder通信机制的基础。
要理解Binder通信机制,首先我们要先来了解一下Android的内存空间:
Linux内核模型.png
用户空间:用户空间的进程是相互独立的,彼此之间不能共享
内核空间:内核空间的内存是可以共享的,其大小可以通过参数来配置
Android进程间通信其实质就是利用内核空间共享来完成的。
下面我们来看Binder进程间通信的四个重要角色:
Binder通信的四个角色.png
- Client:用户端进程
- Server:服务端进程
- Service Manager:运行在用户空间,它负责管理 Service 注册与查询等操作
- Binder驱动:负责进程之间Binder通信的建立,进程间通信的核心
下面我们来总结一下Binder机制的整个运行过程:
- ServiceManager 初始化
- Server 向 ServiceManager 注册自己
- Client 获取远程服务
- Client 向 Server 发起 IPC 请求
- Binder 驱动将该请求转发给 ServiceManager 进程,
- ServiceManager 查找到 对应的 Server 的 Binder 引用后通过 Binder 驱动反馈给 Client(并不是实际真实的远程Binder对象,而是Binder驱动里的一个映射)
- Client 收到 Server 对应的 Binder 引用后,会创建一个 Server 对应的远程服务(即 Server 在当前进程的代理)
- Client 通过代理调用 Server
- Client 会先将请求数据从用户空间拷贝到内核空间,然后调用transact去连接Binder驱动
- 接着Binder驱动会去连接远程进程,并通知远程进程执行onTransact()函数
- Server 返回结果
- 执行完成后将结果写入内核空间
- Binder驱动再唤醒客户端线程获取数据
注意:这个过程中客户端当前线程会被挂起!因此,如果远程进程是执行长时间的运算,请不要使用主线程去调用远程函数,以防止ANR。
示例分析
上面讲了一些理论上的Binder进程间通信的知识,可以清晰的看出Binder采用的是C/S架构的模式来进行进程间通信的,而其核心点也就是内核空间的Binder驱动。下面呢,我们会通过一个具体的实例从源码的角度来解析Binder的进程间通信。
首先我们这里给出一副价值200万的图(一点也不夸张哈!)
Binder通信架构.png
- AIDL(Android Interface Definition Language),即Android接口定义语言。通俗来讲就是定义一个大家都懂的语言,这样进程间交流才不会又障碍。
- Stub:接收底层C/C++Binder引用的回调。
- function:就是我们定义的一些接口方法。
- Proxy:用来访问底层的Binder驱动。
- 本地服务:就是在我们当前进程里的服务(后面会有具体的涉及)。
- Binder驱动:这里看图就行了,不做过多解释。
OK!价值200万的图大家赶紧收好。接下来我们就具体来分析源码。
一、AIDL创建
- 新建com.wf.testaidl.bean.Person类,并实现Parcelable
public class Person implements Parcelable {...}
- 在main下面新建aidl文件夹并创建aidl文件(注意:包名和类名必须相同)
- 编写aidl接口
// Person.aidl
package com.wf.testaidl.bean;
// Declare any non-default types here with import statements
parcelable Person;
Preson.aidl做一下映射避免下面文件编译的时候报错。
// PersonAidl.aidl
package com.wf.testaidl;
// Declare any non-default types here with import statements
import com.wf.testaidl.bean.Person;
interface PersonAidl {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
/*void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);*/
void addPerson(in Person person);
List<Person> getPersonList();
}
这个文件就具体定义了我们需要的接口。
- Make Project
编译成功后会生成app\build\generated\source\aidl\debug\com\wf\testaidl\PersonAidl.java文件。
二、解析PersonAidl.java源码
package com.wf.testaidl;
public interface PersonAidl extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.wf.testaidl.PersonAidl {
...
private static class Proxy implements com.wf.testaidl.PersonAidl {...}
...
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*//*void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);*/
public void addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException;
public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException;
}
是不是很熟悉,没错就是我们上面那幅200万的图。这里可以很清晰的看出PersonAidl里面包含了我们定义的接口方法和一个静态内部抽象类Stub,这个Stub继承了Binder并且实现了PersonAidl接口,这个Stub里面又定义了一个Proxy同样实现了PersonAidl接口。
Stub
ok!接下来我们来看看这个神奇Stub。
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.wf.testaidl.PersonAidl {
private static final java.lang.String DESCRIPTOR = "com.wf.testaidl.PersonAidl";
public Stub() {...}
/**
* Cast an IBinder object into an com.wf.testaidl.PersonAidl interface,
* generating a proxy if needed.
*/
public static com.wf.testaidl.PersonAidl asInterface(android.os.IBinder obj) {...}
@Override
public android.os.IBinder asBinder() {...}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {...}
private static class Proxy implements com.wf.testaidl.PersonAidl {...}
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
上面就是这个Stub的整个的一个结构,这里有两个方法名映射的int常量,这个后面我们会用到。
下面我们就来一步一步的分析它里面的代码。
- DESCRIPTOR
private static final java.lang.String DESCRIPTOR = "com.wf.testaidl.PersonAidl";
这个很简单吧,就是用一个静态常量来存储了我们AIDL的类名,有什么用呢?后面你就知道了。
- Stub() 构造函数
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
构造函数里面就调了一个方法,而这个attachInterface自己没有,那么是谁的呢?没错,是它爹Binder的。
private IInterface mOwner;
private String mDescriptor;
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
那么,这个方法干了一个什么事儿呢?它就是把自己和这个DESCRIPTOR保存在了当前这个类里,做了一个关联关系,那么有什么用呢?我们继续往后面看。
- asInterface()
public static com.wf.testaidl.PersonAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.wf.testaidl.PersonAidl))) {
return ((com.wf.testaidl.PersonAidl) iin);
}
return new com.wf.testaidl.PersonAidl.Stub.Proxy(obj);
}
这个方法主要就干了一个事儿,那就是当发起IPC请求时把我们从Binder驱动拿到的这个IBinder描述转换成一个具体的AIDL接口对象,后面才能调用相应的接口方法。这里面首先调用了一个queryLocalInterface,我们来看看为什么要先调用这个方法呢?
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
这里可以看出它是拿这个descriptor去判断是不是调用的自己,是就直接返回自己(明白了上面那个attachInterface的作用了吧)。这又是为什么呢?这里就有一个要注意的点了,上面我们给出的那个200万的图是不是有一个本地服务,那么为什么会有一个本地服务呢?这里我就要反问一个问题了:是不是我们需要访问的服务一定是在不同的进程呢?答案是不是的,我们也有可能会访问自己的服务,对吧!好了,那么我想你应该明白了这里为什么会有一个queryLocalInterface了吧。
好了,我们接着往下看,如果我们访问的不是自己就需要拿这个IBinder描述去创建一个远程的AIDL接口对象。
- onTransact()
@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_addPerson: {
data.enforceInterface(DESCRIPTOR);
com.wf.testaidl.bean.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.wf.testaidl.bean.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getPersonList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.wf.testaidl.bean.Person> _result = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
这个方法就是来处理我们的IPC请求的,根据不同的code值最终会调到它的实现类的对应的接口方法,然后将处理后的结果写入replay返回给客户端。这里面的代码都很简单,就不一一分析了,相信大家自己也能看明白。
- class Proxy这个我们单独放到下一节来讲。
- 此刻我想你一定有一个问题:服务不是要注册到ServiceManager嘛,上面没有哪里讲到注册了啊?其实它在我们上面讲的构造函数里面就已经完成了注册,无参构造函数的初始化首先会调用父类的无参构造函数,那我们就来看看它的父类Binder的无参构造函数:
public Binder() {
init();
...
}
private native final void init();
从上面我们可以看出它调用了一个native方法,就是在这里进行注册的。那么你又会问了:这里没有传任何数据啊?其实这里native方法它会隐式的把this传过去,那我们之定义的DESCRIPTOR就注册过去了。
到这里我们的Stub服务端就基本讲完了。
Proxy
上面我们剖析了一下Stub的源码,理解到了Stub其实是作为一个服务端注册到ServiceManager来响应我们的请求的。接下来我们就来看我么是怎么样发起请求的呢?首先我们来看一下这个Proxy是什么。
private static class Proxy implements com.wf.testaidl.PersonAidl {
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 addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException {...}
@Override
public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException { ...}
}
Proxy作为一个Stub的静态内部类主要用来发起跨进程的请求,那一个进程为什么既有Stub又有Proxy呢?因为一个进程它既可以是客户端也可以是服务端。好了,接下来我们一步步分析Proxy的源码,看看它是怎么工作的。
- 构造函数
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
初始化的时候传入一需要访问的进程的IBinder描述,也就是我们发起IPC请求的时候从ServiceManager里面查询寻到的IBinder,这样我们才知道是要去访问哪个进程。
- 实现接口方法addPerson()和getPersonList()
@Override
public void addPerson(com.wf.testaidl.bean.Person person) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((person != null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.util.List<com.wf.testaidl.bean.Person> getPersonList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.wf.testaidl.bean.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.wf.testaidl.bean.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
首先初始化两个Parcel对象,都是通过Parcel.obtain()去Parcel池(队列)里面拿的(节约内存)。然后将我们要访问的进程(DESCRIPTOR)和传递的参数写入Parcel。接下来就调用IBinder引用的transact()去连接 Binder驱动。
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
这里有一个很有意思也是最简单的做法,它把要访问的方法定义成了一个int类型的常量TRANSACTION_addPerson和TRANSACTION_getPersonList,这两个常量是定义在它外部类stub里面的,这样IBinder引用拿到后就知道是要去调用哪个方法了。
接下来我们看看这个transact()做了些什么事情?
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
当Binder驱动拿到我们的请求和数据后,它就会去连接我们要访问的服务端进程,然后通知它回调onTransact()方法。到这里我们的整个请求就算完成了。
小结
- 一个进程既可以是服务端Stub,也可以是客户端Proxy。
- 一个IPC请求发起时,首先会调用Proxy去连接Binder驱动,然后Binder驱动再去连接Stub
- 要实现跨进程通信,两个进程必须要又相同的AIDL接口
三、调用流程分析
- ServiceManager的注册和获取
上面我们讲到了Binder进程间通信时所有的Server都时由ServiceManager来管理的,那么你可能会问了ServiceManager也时一个独立进程,那么它是什么时候启动?又是什么时候注册的呢?
系统启动的时候回去执行\system\core\rootdir\init.rc,在这里面就启动了ServiceManager,启动之后会去执行service_manager.c,那这里面干了什么呢?
int main(int argc, char **argv)
{
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;
bs = binder_open(128*1024);
if (binder_become_context_manager(bs)) {
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}
打开bind驱动,并且分配128K大小,紧接着把自己注册为Service 大管家,然后就开启loop来读取Binder驱动的消息处理各种服务请求。
- 客户端发起IPC请求
private PersonAidl mPersonAidl;
//绑定服务
Intent intent = new Intent(this, AidlService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPersonAidl = PersonAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mPersonAidl = null;
}
}, BIND_AUTO_CREATE);
//请求接口
mPersonAidl.addPerson(person);
List<Person> personList = mPersonAidl.getPersonList();
上面我们是通过显示意图来绑定的,因为我们是写在一起然后配置在不同进程的,当然你也可以用隐式意图来绑定服务,那么接下来我们就来看看bindService做了什么?
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
return mBase.bindService(service, conn, flags);
}
那我们就来看看mBase.bindService又是干了什么?
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
我们可以看到它调用的是一个抽象方法,那么我们就自然要去找到它的实现类,最终我们找到了ContextImpl.java里面。
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags){
warnIfCallingFromSystemProcess();
return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser());
}
那么我再来看看 bindServiceCommon又干了些啥?
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user){
...
int res = ActivityManager.getService().bindService(...)
...
}
好,到这里就开始跨进程访问了,通过ActivityManagerService去获取远程服务,最终会在ActivityThread.java的handleBindService()方法里回调远程服务的onBind方法返回一个IBinder引用,这样我们就能通过这个IBinder引用去调用相应的接口来进程跨进程访问。后面具体的底层源码这里就不贴出来了,感兴趣的话可以自己去翻阅源码。
- 自定义服务端注册
public class AidlService extends Service {
private List<Person> mPersons = new ArrayList<>();
private IBinder mIBinder = new PersonAidl.Stub() {
@Override
public void addPerson(Person person) throws RemoteException {
mPersons.add(person);
}
@Override
public List<Person> getPersonList() throws RemoteException {
return mPersons;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mIBinder;
}
}
上面的代码就是我们自定义的一个服务,它里面实现了我们的PersonAidl.Stub,上一节我们分析的Stub源码里的onTransact最终会回调到这里我们具体的实现。并且这里可以看到它里面实现了一个onBind方法来返回这个IBinder引用。
<service
android:name=".service.AidlService"
android:exported="true"
android:enabled="true"
android:process=":aidl"/>
上面这段代码就是把我们的服务声明到aidl进程,这里没有什么要说的,都是很标准的写法。
总结
好了,到这里呢,我们这次的Binder进程间通信机制的探索就告一段落了。主要掌握以下几点:
- Linux内存空间分为用户空间和内核空间,用户空间不能共享,内核空间可以共享;
- Binder机制的四个重要角色:Client、Server、ServiceManager、Binder驱动;
- Binder机制采用的是C/S架构,Binder驱动是他们之间的桥梁亦是整个架构的核心。
- Android中Binder主要的表现形式就是AIDL,重点理解Stub和Proxy模式。
以上就是我个人对Binder机制的一个理解,如果有说错的或者理解不到位的地方都可以指出来,我们一起学习改正,谢谢!
最后我们给出举例的源码地址:https://github.com/WangFion/TestAidl
网友评论