8.1 Binder 简介。
Binder,英文的名称是别针、回形针。现实中,我们经常会用回形针把纸张别起来,而在Android中,Binder是用于进程通信,它负责把不同的进程“别”起来,使得不同进程可以一起工作。比如,在导航软件中,我们可以控制音乐的暂停、播放。
Binder工作再Linux层,属于一个驱动,只是这个驱动不需要硬件,或者说,它操作的硬件是基于一小段内存。从线程的角度来说,Binder是运行在内核态,客户端调用Binder是通过
系统调用完成的。
Binder是Android进程通信的中最常用的方式,它的原理是通过访问共享内存来实现的,因此,Binder的效率最高,它使得各个组件可以互相通信。进程间传输数据时只需拷贝一次,传统的IPC需拷贝两次。因此使用binder可大大提高IPC通信效率。
直观来说,Binder是Android的一个类,实现了IBinder接口,从IPC角度说,Binder是Android中一种跨进程通信的方式,Bindr还可以理解为一种虚拟的物理设备,设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework的角度上来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager、等等)和相应ManagerService的桥梁,服务端会返回一个包含了服务端业务调用的Binder对象,通过Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括了普通的服务和基于AIDL的服务。
下面我们先从一个AIDL开始入手这个Binder:
// Book.java
public class Book implements Parcelable {
public String bookName;
public Book( String bookName )
{
this.bookName = bookName;
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel arg0, int arg1) {
arg0.writeString(bookName);
}
public static final Parcelable.Creator< Book> CREATOR = new Creator<Book>() {
@Override
public Book[] newArray(int arg0) {
// TODO Auto-generated method stub
return new Book[arg0];
}
@Override
public Book createFromParcel(Parcel arg0) {
// TODO Auto-generated method stub
return new Book(arg0);
}
};
public Book( Parcel in )
{
bookName = in.readString();
}
}
//Book.aidl
package com.zhenfei.aidl;
parcelable Book;
//IBookManager.aidl
package com.zhenfei.aidl;
import com.zhenfei.aidl.Book;
interface IBookManager{
List<Book> getBookList();
void addBook( in Book book);
}
上面三个文件当中,Book.java是表示书本信息的类,实现了Parcelable接口,在AIDL中使用Book这个类,需要以AIDL的形式声明Book,如:Book.aidl。IBookManager.aidl是我们定义的一个接口,这个接口有2个方法,是用来从远程服务器获取图书列表,而addBook用于往图书列表当中添加一本书。另外,虽然Book和IBookManager是在同一个包里面,不过也需要手动导Book类。之后,我们会发现,在gen文件夹中,自动生成了IBookManager.java类,这个是系统生成的,现在我们从这个类开始分析Binder的工作原理:
package com.zhenfei.aidl;
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.zhenfei.aidl.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.zhenfei.aidl.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.zhenfei.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.zhenfei.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.zhenfei.aidl.IBookManager))) {
return ((com.zhenfei.aidl.IBookManager)iin);
}
return new com.zhenfei.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.zhenfei.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.zhenfei.aidl.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.zhenfei.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.zhenfei.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.zhenfei.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.zhenfei.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.zhenfei.aidl.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.zhenfei.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 {+*-999999999
_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.zhenfei.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.zhenfei.aidl.Book book) throws android.os.RemoteException;
}
这个类看起来是比较乱,但是,我们现在先一部分一部分的看,就会发现,这个其实是逻辑很清楚的。
首先,我们看IBookManger这个类,抛开里面的内部类,我们就知道,这个类其实就是定义了2个接口方法,也就是getBookList(),和addBook()方法。注意的是,这个接口集成了android.os.IInterface这个接口,AIDL中,可以在Binder中传输的接口都需要集成IInterface接口。
官方对于IInterface的描述如下:
Base class for Binder interfaces. When defining a new interface, you must derive it from IInterface.
表示:这个是Binder中的接口的基类。当我们定义一个新的接口的时候,必须要从派生这个接口,这个接口的实际上做的就是,把Binder转为IIterface,或者是把IInterface转为一个Binder对象。
接下来看IBookManager中的Stub类,这个类是一个抽象类,因为IBookManager的两个方法它并没有去实现。并且这个Stub类继承与Binder,因此,这个类就是我们现在要讨论的。
到此,我们先不看这个类里面的方法,我们继续完善AIDL模块的编写吧。
接下来是服务端的实现代码:
public class ServerService extends Service{
ArrayList<Book> books = new ArrayList<Book>();
private Stub stub = new Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return books;
}
@Override
public void addBook(Book book) throws RemoteException {
books.add(book);
}
};
public void onCreate() {
super.onCreate();
Book book1 = new Book( "第1本书");
Book book2 = new Book( "第2本书");
Book book3 = new Book( "第3本书");
books.add(book1);
books.add(book2);
books.add(book3);
System.out.println( "有 "+ books.size() + " 本书");
};
@Override
public IBinder onBind(Intent arg0) {
return stub;
}
}
可以看到,Stub这个类的接口方法,是在服务端实现的,并且,服务端会在onBind这个方法返回这个stub对象。
下面我们看下客户端的代码:
public class MainActivity extends Activity {
IBookManager iBookManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent toNewIntent = new Intent();
toNewIntent.setAction( "com.zhenfei.myaidl");
bindService(toNewIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
public ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName arg0) {
System.out.println( "onServiceDisconnected className :" + arg0.getClassName()) ;
}
@Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
iBookManager = IBookManager.Stub.asInterface(arg1);
System.out.println( "onServiceConnected ");
List<Book> books;
try {
books = iBookManager.getBookList();
System.out.println( "客户端获得到: "+ books.size() + " 本书");
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
}
结合上面的代码以及服务端的代码,我们可以知道,服务端返回binder对象stub,然后在客户端接收这个对象,然后,我们就是用这个对象来进行远程通信,那么这个是怎么做到的?客户端和服务端是运行在不同进程的,他们是如何获取到这个对象的呢?这个的关键就在于IBookManager的Stub类:
下面,我们从Stub类开始入手:
Stub类的构造函数:
我们可以看到构造函数调用了这个方法:attachInterface( this , DESCRIPTOR );,其中参数是一个常量的字符串,DESCRIPTOR
一般是用于标识Binder,一般是用这个Binder的类名表示。
attachInterface是Binder类中的一个方法:
/**
* Convenience method for associating a specific interface with the Binder.
* After calling, queryLocalInterface() will be implemented for you
* to return the given owner IInterface when the corresponding
* descriptor is requested.
*/
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
可以看到,其实,在本例中这个方法就是,把IBookManager的对象绑定到了这个Binder当中,并且告知了这个Binder它自己叫什么。
接下来往下看到了asInterface方法,这个方法的作用从输入和输出来说,他就是把一个Binder对象转化为IBookManager对象。
接下来来研究这个代码,我们看到如下代码:
android.os.IInterface iin = obj.queryLocalInterface( DESCRIPTOR);
这一行的作用是什么?我们看下queryLocalInterface的实现,这个方法在Binder中。
/**
* Use information supplied to attachInterface() to return the
* associated IInterface if it matches the requested
* descriptor.
*/
public IInterface queryLocalInterface(String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
从上面的代码可以看到,其实他就是匹配下Binder对象中的descriptor是否和参数一致,如果一致,就说明这个Binder就是我们想要的,因为descriptor一样,就说明IIterface的类型也是一样的,那么实际上是一样的吗?
我们知道服务端和客户端的Stub的代码是一样的,那么,照常理来说,服务端的对象传递到客户端的话,那么这个queryLocalInterface总是返回的不是null,但是实际上,不同进程调用这个方法,得到的就是null,除非是相同进程调用。那就说明一件事,客户端这边在onServiceConnected这个方法中,参数传递过来的对象不是服务端的对象。
正常大家理解的Binder机制是这样子的:
这个其实并没有什么问题,只是它其实是服务端进程内部的Binder机制。
而远程客户端的Binder机制其实是下面这样的:
上面这个图稍微凌乱了一点,其实意思就是,当你在服务端进程创建一个对象的时候,Binder驱动也会创建一个Binder对象,如果你是从远程获取服务端的Binder,则只会返回Binder驱动的Binder对象。而如果从服务端内部进程获取Binder对象,那么就会返回服务端的Binder对象,这也就是为什么queryLocalInterface这个方法,在不同进程调用返回的内容不一样。因为驱动内部的Binder对象,它的descriptor不是我们指定的那个descriptor。
Binder驱动的Binder对象和服务端的Binder对象的关联,是通过地址映射做到的,这里就不详细说了(使用的是内存映射技术,其实我也不是很懂,就不胡说八道了。),这里就认为,Binder驱动Binder和服务端的Binder对象可以通过内核的驱动层直接互相调用就可以了。
那好,到这里,我们就知道,服务端的Binder对象,我们可以通过Binder驱动的IBinder对象进行访问,但是,Binder驱动的对象很明显不是一个Stub对象,既然不是一个Stub对象,那么我们怎么调用Stub类里面的方法呢?
我们先继续看asInterface这个方法,可以看到,在远程调用的情况下,返回的代码是这样子的:
return new com.zhenfei.aidl.IBookManager.Stub.Proxy(obj);
也就是说,返回的是一个Proxy对象,我们看Proxy类的代码,可以知道,Proxy是实现了IBookManager的接口方法,因此,在远程客户端,调用的getBookList()方法,其实是调用Proxy对象的getBookList方法,Proxy在构造函数中,已经保存了Binder驱动的IBinder对象,前面我们已经知道了这个对象是可以映射到服务端的Binder对象,但是它不是一个IBookManager接口的对象,那么,Proxy是如何访问服务端的Binder对象的IBookManager的接口方法呢?
Proxy的getBookList代码是这样子的:
<span style="white-space:pre"> </span>android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.zhenfei.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.zhenfei.aidl.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
下面来一行行地解释,首先_data 和_reply 是从Parcel中申请的而不是客户端这边创建的,申请的Parcel可以让我们读取到不同进程的数据,接着往下看,
_data.writeInterfaceToken(DESCRIPTOR);
这一行是用来标注远程服务的名称,这个不是必须要的,因为我们调用的remote已经确保我们或得到了远程的Binder引用。
接下来往下看,看到了:
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
Binder驱动的IBinder对象调用这个方法,是调用哪个transact方法呢?调用的是服务端的transact方法,前面我们说过了Binder驱动的IBinder对象它内存映射的是服务端的Binder的对象的地址,而方法也是在内存中有地址的,我们调用的就是服务端的Binder对象的方法了,那么服务端的transact方法就是Stub方法了,那我们就往下阅读服务端的transact代码:
@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.zhenfei.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.zhenfei.aidl.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.zhenfei.aidl.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
data.enforceInterface 这个方法应该是验证writInterfaceToken是否正确。(我不确定- - )
<span style="white-space:pre"> </span> java.util.List<com.zhenfei.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
这就是服务端获取Book的列表,并且写入Parcel中,然后写入到reply包中,我们前面说过了,reply是Parcel数据,他可以在不同进程中传递数据,我们前面学过,知道Parcel序列化数据以后,在不同进程中传递,因此,在客户端的IBinder对象,我们就可以从reply获取到Book的列表了。(Parcel机制在这边就不详细讨论,之后有空会继续写,Parcel机制设计的native层更多一点)0
因此,总结Proxy#getBookList()
这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样子的:首先创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值对象对象List;然后把该方法的参数信息写入_data中(如果有参数的话),接着transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起,然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前的线程继续执行,并且从_reply中取出RPC过程的返回结果,最后返回_reply的数据。这里面有个TRANSACTION_getBookList 和TRANSACTION_addBook 这几个常量,作用很明显,在此就不赘述。
然后我们看,Proxy#addBook,
这个方法调用的流程和getBookList差不多,就是一些细节不一样,addBook是客户端添加数据到服务端,所以调用parcel.writeString方法来写入数据。这里要注意的是,我们在aidl文件中,addBook( in Book book)是这样子的,前面的in是代表参数的方向,不同的方向,如:in、out、inout生成的代码是不一样的。下面简单介绍一下in、out、inout的区别,参考的文章是:
http://hold-on.iteye.com/blog/2026138
如果client不需要传输数据给server,client只需要处理经过server处理过后的数据,
那么 client 和 server 都为 out
如果client只需要传输数据给server,而不需要处理返回的数据,
那么client和server都为 in
如果client需要传输数据给server,而且需要处理返回的数据,
则client和server都为 inout
好了,在这边Binder就先介绍到这里了,如果之后有对C++层和内核层的Binder机制进行学习,我会继续写下来。
下面是本文测试Demo的地址: