安卓IPC机制之Binder详解
IPC(Inter-Process Communication,跨进程通信)是指两个进程之间数据交换的过程,因此我们首先必须了解什么是进程,什么是线程。
进程:进程是正在运行的程序的实例,与程序相比,它更强调动态的概念,与线程相比,进程是线程的容器,一个进程可以包含多个线程但至少包含一个线程。进程是任务调度的基本单位,是系统资源的分配单位。
线程:线程是进程中的一条执行路径,它只能隶属于某一个进程,在支持多线程的操作系统或程序设计语言中(如java),线程是任务调度的单位,但它不是系统资源分配的单位,线程完全继承父进程占有的资源,但它执行时,也有属于自己的运行现场,如PC寄存器的值,栈空间等。
关于进程与线程内存资源方面的知识请参看我的博文:http://blog.csdn.net/htq__/article/details/50822582。
IPC机制不是安卓独有的,任何一个操作系统都需要IPC机制,如linux中可以通过命名管道,共享主存,信号量,Socket,消息队列等方式来呢实现IPC机制,安卓虽然是基于linux内核的,但是安卓中的IPC机制与linux不完全相同,如Binder机制就是安卓的IPC中的一大特色,这种IPC机制在linux中是不存在的。本博客将详细讲解安卓中的Binder机制。
在安卓开发中Binder主要用在Service中,包括AIDL与Messenger,而Messenger的底层仍然是AIDL,关于这些内容,读者可以参看我的博客:点击打开链接。所以本博客将以AIDL为例详细讲解Binder的IPC原理。
首先创建一个AIDL的java包,在该包中创建三个文件Book.java,Book.aidl和IBookManager.aidl代码如下:
public class Book implements Parcelable { //Book.java public int bookId; public String bookName; public Book() { } public Book(int bookId, String bookName) { this.bookId = bookId; this.bookName = bookName; } public int describeContents() { return 0; } public void writeToParcel(Parcel out, int flags) { out.writeInt(bookId); out.writeString(bookName); } public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() { public Book createFromParcel(Parcel in) { return new Book(in); } public Book[] newArray(int size) { return new Book[size]; } }; private Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } @Override public String toString() { return String.format("[bookId:%s, bookName:%s]", bookId, bookName); } } parcelable Book;//Book.aidl interface IBookManager { //IBookManager.aidl List<Book> getBookList(); void addBook(in Book book); }
其中Book.java是一个表示图书信息的java类,它实现了Parcelable接口,Book.aidl是Book类在AIDL中的声明,IBookManager.aidl是我们定义的一个接口,注意尽管Book类与IBookManager类位于同一个包中,但是在IBookManager.aidl文件中仍需要导入Book类,这是aidl文件与普通java的class文件的不同之处。
当我们定义好上述文件后Eclipse会自动为我们在工程的gen目录下生成一个与IBookManager.aidl文件相对应的java类IBookManager.java,我们来看一下其源码:
public interface IBookManager extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.test.aidl.IBookManager { private static final java.lang.String DESCRIPTOR = "com.test.aidl.IBookManager"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.test.aidl.IBookManager interface, * generating a proxy if needed. */ public static com.test.aidl.IBookManager asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.test.aidl.IBookManager))) { return ((com.test.aidl.IBookManager)iin); } return new com.test.aidl.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<com.test.aidl.Book> _result = this.getBookList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBook: { data.enforceInterface(DESCRIPTOR); com.test.aidl.Book _arg0; if ((0!=data.readInt())) { _arg0 = com.test.aidl.Book.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addBook(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.test.aidl.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<com.test.aidl.Book> getBookList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.test.aidl.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.test.aidl.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBook(com.test.aidl.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(); } } } static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); public java.util.List<com.test.aidl.Book> getBookList() throws android.os.RemoteException; public void addBook(com.test.aidl.Book book) throws android.os.RemoteException; }可以看到系统为我们自动创建的IBookManager文件仍然是一个接口,它继承自android.os.IInterface。在其内部创建了一个抽象的静态类Stub,它继承自Binder类同时实现了IBookManager接口(但未实现该接口中的方法,因为该接口中的方法是我们在创建一个Service时在其内部创建Binder类时实现的,因此该Stub仍然属于一个抽象类)。
我们首先来梳理一下IBookManager接口的内容,可以看到,首先包含两个方法的声明getBookList和addBook,显然这两个方法是我们在IBookManager.aidl文件中声明的两个方法,另外它还定义了两个整型的id用于标识这两个方法,这两个id在其内部代理类Proxy的方法的transact过程中用于判断客户端请求的是哪一个方法。另外还包含一个内部类Stub,该Stub是一个Binder类,在这个Stub类中定义了一个内部类Proxy。
下面介绍一下上述代码中的一些重要方法:
public static com.test.aidl.IBookManager asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.test.aidl.IBookManager))) {//此时表示客户端与服务端位于同一个进程中,直接返回IInterface对象 return ((com.test.aidl.IBookManager)iin); } return new com.test.aidl.IBookManager.Stub.Proxy(obj);<span style="font-family: Arial, Helvetica, sans-serif;">//此时表示客户端与服务端不在同一个进程中,此时返回代理类Proxy对象</span> }asInterface正是我们在bindServide的组件中使用ServiceConnection时首先要使用的一个方法,可以看到在方法中首先通过obj.queryLocalInterface(DESCRIPTOR)根据传入的参数获取IInterface对象,然后判断该对象是否为IBookManager类,若是则直接返回该对象,此时表示客户端与服务端位于同一个进程中,即绑定的是本地服务,即在ServiceConnection的onServiceConnected中我们是这么调用的Binder对象
public void onServiceConnected(ComponentName name, IBinder service) { myBinder = (MyService.MyBinder) service; //绑定的是本地服务 myBinder.doSomething(); }
否则返回IBookManager类的内部代理对象IBookManager.Stub.Proxy(obj),也就意味着客户端与服务端位于不同的进程中,此时客户端调用的方法将会是Proxy代理类中定义的方法,而不是直接调用的在Service中实现的IBookManager.aidl文件中的方法。即绑定的是远程服务,即在ServiceConnection的onServiceConnected中我们是这么调用的Binder对象
public void onServiceConnected(ComponentName name, IBinder service) { myBinder = MyAIDL.Stub.asInterface(service) //绑定的是远程服务 myBinder.doSomething(); }
通过上面的分析可知,当绑定的是远程服务时,当在客户端调用addBook将会调用代理类中的 addBook方法,因此我们接下来看一下Proxy#addBook
@Override public void addBook(com.test.aidl.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(); } }可以看到在该方法中首先创建了一个输入型参数Parce对象_data与输出型Parcel对象_reply,然后向该输入型参数Parce对象_data中写入一些数据,然后调用了mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);语句,当执行该语句时,即表示客户端发起RPC(远程过程调用)请求,此时当前线程会被挂起,直至RPC过程返回后,当前线程继续执行。注意该mRemote对象即为在创建该Proxy对象时传入的Binder对象,即mRemote是一个Binder对象,我们看一下Binder中的transact函数,代码如下:
/** * Default implementation rewinds the parcels and calls onTransact. On * the remote side, transact calls into the binder to do the IPC. */ public final boolean transact(int code, Parcel data, 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; }
可以看到transact函数被final修饰,即该函数是不允许被继承重写的,在该函数中最重要的一个语句: boolean r = onTransact(code, data, reply, flags);即在transact函数中调用了onTransact函数,而onTransact函数一般是要求被重写的,而重写的过程是根据我们自己定义的aidl文件IDE自动帮我们完成的,如上面所示的根据IBookManager.aidl文件自动生成IBookManager.java文件,在这个IBookManager.java文件中onTransact函数被重写。
这个onTransact函数是Binder机制运行的关键,该函数运行在服务端的Binder线程池中,当客户端发起跨进程请求时,即调用transact函数时,远程请求的数据会通过系统底层交给该函数来执行,该函数的完整签名为: public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端通过code可以确定客户端调用的是那个方法,然后从data中取出目标方法所带的参数(如果目标方法带参数的话),然后执行目标方法,当目标方法执行完毕后,就向reply中写入返回值(如果目标方法存在返回值的话),需要注意的是如果该函数返回false,则客户端的请求会失败,利用该特性可以做权限验证,因为我们也不希望任意一个进程都可以随意访问我们的服务。
通过上述的分析相信读者对Binder的运行机制的整个过程已经非常熟悉了,至于系统底层的Binder是如何传输数据的这个不重要,也不需要我们去理解,我们只需要知道Binder的运行机制的整个过程,下面用图示的方式来讲上述过程表示出来。注意下面图示仅仅表示绑定远程服务的过程,因为绑定本地服务不涉及IPC机制。如下:
对上述图示还是稍作解释,首先在客户端Client的ServiceConnection的onServiceConnected(ComponentName name, IBinder service) 方法中我们是通过 myBinder = MyAIDL.Stub.asInterface(service)语句将service转换为Binder对象,即调用的是asInterface方法而不是使用(MyService.MyBinder) service的类型转换的方式,则表示我们要获取的是远程服务而不是本地服务,那么接下来 myBinder调用的addBook(book);这个方法将会在其内部调用Binder的transact方法用来发起RPC请求,此时客户端Client被挂起,Binder的transact方法是一个final方法,不允许被修改,在该方法中调用了onTransact方法,该方法运行在服务端的Binder线程池中,该方法是整个Binder机制运行的关键,在该方法中完成IPC数据的读取与写入,该方法返回值将写入_reply参数中,然后RPC过程结束,返回数据给客户端,客户端Client被唤醒。
另外因为服务端的Binder方法是运行在Binder线程池中的,所以Binder中的方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中。