8.Binder详解

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的服务。

8.2 从AIDL开始学习Binder    

下面我们先从一个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的地址:

猜你喜欢

转载自blog.csdn.net/savelove911/article/details/51288577