浅谈Android进程间通讯(Binder)
进程间通讯IPC不是Android中所特有的,任何一个操作系统需要相应的IPC机制。理解Binder对于理解整个Android系统有着非常重要的作用,Android系统的四大组件,AMS,PMS等系统服务无一不与Binder挂钩。
本文主要从以下几个方面讲解Binder:
- linux进程间通信相关背景知识
- 图解Binder通信模型
- Java层的Binder
- AIDL使用详解
- serverManager进程与client进程或server进程的交互
1. linux进程间通信相关知识
进程隔离
进程隔离是为保护操作系统中进程互不干扰而设计的一组不同硬件和软件的技术。这个技术是为了避免进程A写入进程B的情况发生。 进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就防止进程A将数据信息写入进程B。
用户空间/内核空间
Linux Kernel 是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。
对于Kernel这么一个高安全级别的东西,显然是不容许其它的应用程序随便调用或访问的,所以需要对Kernel提供一定的保护机制,这个保护机制用来告诉那些应用程序,你只可以访问某些许可的资源,不许可的资源是拒绝被访问的,于是就把Kernel和上层的应用程序抽像的隔离开,分别称之为Kernel Space和User Space。
内核模块/驱动
通过系统调用,用户空间可以访问内核空间,那么如果一个用户空间想与另外一个用户空间进行通信怎么办呢?很自然想到的是让操作系统内核添加支持;传统的Linux通信机制,比如Socket,管道等都是内核支持的;但是Binder并不是Linux内核的一部分,它是怎么做到访问内核空间的呢?Linux的动态可加载内核模块(Loadable Kernel Module,LKM)机制解决了这个问题;模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。这样,Android系统可以通过添加一个内核模块运行在内核空间,用户进程之间的通过这个模块作为桥梁,就可以完成通信了。
在Android系统中,这个运行在内核空间的,负责各个用户进程通过Binder通信的内核模块叫做Binder驱动;
驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作;
驱动就是操作硬件的接口,为了支持Binder通信过程,Binder使用了一种“硬件”,因此这个模块被称之为驱动
2. Binder通信模型
其中Server,Client,SMgr运行于用户空间,驱动运行于内核空间。
整个通信步骤如下:
I. SM建立(建立通信录);首先有一个进程向驱动提出申请为SM;驱动同意之后,SM进程负责管理Service(注意这里是Service而不是Server,因为如果通信过程反过来的话,那么原来的客户端Client也会成为服务端Server)不过这时候通信录还是空的,一个号码都没有。
II. 各个Server向SM注册(完善通信录);每个Server端进程启动之后,向SM报告,我是zhangsan, 要找我请返回0x1234(这个地址没有实际意义,类比);其他Server进程依次如此;这样SM就建立了一张表,对应着各个Server的名字和地址;就好比B与A见面了,说存个我的号码吧,以后找我拨打10086;
III. Client想要与Server通信,首先询问SM;请告诉我如何联系zhangsan,SM收到后给他一个号码0x1234;Client收到之后,开心滴用这个号码拨通了Server的电话,于是就开始通信了。
Binder驱动保存了每一个跨越进程的Binder对象的相关信息;在驱动中,Binder本地对象的代表是一个叫做binder_node的数据结构,
Binder代理对象是用binder_ref代表的。
3. java层Binder
1. AIDL过程分析
首先server端的aidl接口
interface IBookManager {
List getBookList();
void addBook(in Book book);
void registerListener(in OnNewBookArrivedListener onNewBookArrivedListener);
void unregisterListener(in OnNewBookArrivedListener onNewBookArrivedListener);
}
编译之后系统生成IBookManager
package com.chehejia.aidlserver;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.chehejia.aidlserver.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.chehejia.aidlserver.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.chehejia.aidlserver.IBookManager interface,
* generating a proxy if needed.
*/
public static com.chehejia.aidlserver.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.chehejia.aidlserver.IBookManager))) {
return ((com.chehejia.aidlserver.IBookManager) iin);
}
return new com.chehejia.aidlserver.IBookManager.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_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.chehejia.aidlserver.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.chehejia.aidlserver.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_registerListener: {
data.enforceInterface(DESCRIPTOR);
com.chehejia.aidlserver.OnNewBookArrivedListener _arg0;
_arg0 = com.chehejia.aidlserver.OnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
this.registerListener(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_unregisterListener: {
data.enforceInterface(DESCRIPTOR);
com.chehejia.aidlserver.OnNewBookArrivedListener _arg0;
_arg0 = com.chehejia.aidlserver.OnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
this.unregisterListener(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.chehejia.aidlserver.IBookManager {
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.util.List getBookList() 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);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.chehejia.aidlserver.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.chehejia.aidlserver.Book book) 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 ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void registerListener(com.chehejia.aidlserver.OnNewBookArrivedListener onNewBookArrivedListener) 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((((onNewBookArrivedListener != null)) ? (onNewBookArrivedListener.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void unregisterListener(com.chehejia.aidlserver.OnNewBookArrivedListener onNewBookArrivedListener) 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((((onNewBookArrivedListener != null)) ? (onNewBookArrivedListener.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
}
public java.util.List getBookList() throws android.os.RemoteException;
public void addBook(com.chehejia.aidlserver.Book book) throws android.os.RemoteException;
public void registerListener(com.chehejia.aidlserver.OnNewBookArrivedListener onNewBookArrivedListener) throws android.os.RemoteException;
public void unregisterListener(com.chehejia.aidlserver.OnNewBookArrivedListener onNewBookArrivedListener) throws android.os.RemoteException;
}
2. IBinder/IInterface/Binder/BinderProxy/Stub总结
IBinder是一个接口,它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递;这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。
IBinder负责数据传输,那么client与server端的调用契约(这里不用接口避免混淆)呢?这里的IInterface代表就是远程server对象具有什么能力。具体来说,就是aidl里面的接口。
Java层的Binder类,代表的其实就是Binder本地对象。BinderProxy类是Binder类的一个内部类,它代表远程进程的Binder对象的本地代理;这两个类都继承自IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。
在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给 Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要我们手动完成,这里使用了策略模式。
4 AIDL中的注意点
- 客户端注册的listener如果服务端采用普通list存储的话,会造成取消注册失败。原因是客户端注册的listener经过Binder传输到服务端后,会在服务端生成新的listener,导致客户端注册的和解绑的不是同一个对象。那么我们应该使用什么方式进行取消注册的操作呢?答案是 RemoteCallbackList。
客户端调用远程服务端的方法,被调用的方法运行在服务端的 Binder线程池中,同时客户端当前调用线程被挂起,如果服务端的方法有耗时的操作,那么客户端的调用线程必须为子线程来避免ANR。
Binder可能会意外的死亡,需要客户端重新连接服务。这里有两种方法,第一种是给Binder设置DeathRecipient监听,binderDied回调(非UI线程)。第二种是onserverDisconnected回调(UI线程)。
5. ServiceManager与Binder
在Android启动ServiceManager进程的时候,都做了什么事?
- 利用BINDER_SET_CONTEXT_MGR命令,令自己成为上下文管理者,其实也就是成为ServiceManager。
- 将BINDER_SET_CONTEXT_MGR命令传给Binder驱动的时候,Binder驱动就会为其在内核空间中创建一个节点(binder_node),句柄为0。(0号引用)在整个系统中,只会有一个binder_context_mgr_node,所以也只会有一个ServiceManager的进程,那么对ServiceManager的访问,驱动就可以在系统中定义好其句柄,也就是 0。
- 进入一个无限循环,等待Client的请求到来。
server进程注册到serverManager
- 每一个提供服务的Server都会通过Binder驱动,将自身给注册到ServiceManager中。(本质上,就是Server们会将自身作为一个对象,封装在数据包中,将这些数据复制到内核空间中,由Binder驱动访问。)
- Binder驱动读取数据包的时候,如果发现其中有Binder实体,那么也会为对应的Binder实体创建对应的Binder节点(BinderNode)。
- Binder驱动也会为这些服务分配句柄(大于0),同时会将这些句柄也记录在Binder驱动中,然后再将这些句柄和名字发送给ServiceManager,由ServiceManager来维护。
Client进程与serverManager交互
- server服务的名字,加上一个句柄为 0 的值,封装为一个数据包,打开Binder设备文件,将这个数据发送给Binder驱动。
- Binder驱动接收到句柄为0,就会将这数据包扔给ServiceManager。
- ServiceManager接收到这个数据包,就会分析,发现是要找某个名字的服务,于是就找找找,然后将对应服务的句柄发送回来(某大于0的句柄)。
- 驱动再server进程句柄发回给Client。
- Client获取句柄之后,就会再加上想要的服务,还有这个句柄,再发送给Binder驱动,Binder驱动就会找到对应的句柄,然后调用server进程相关服务。
参考
binder学习指南