安卓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中的方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中。







posted on 2016-06-02 12:17  海南一哥  阅读(381)  评论(0编辑  收藏  举报

导航