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







  • 相关阅读:
    找出优先要作的工作
    我要作技术研发了
    确定配色方案
    今天公司搬家
    要作界面原型了
    使用自已的命名空间
    进行审核了
    那里有皮肤软件工开发包的源码???
    发葡萄
    作业务规则挺难
  • 原文地址:https://www.cnblogs.com/hainange/p/6334015.html
Copyright © 2011-2022 走看看