小松的技术博客

六和敬

若今生迷局深陷,射影含沙。便许你来世袖手天下,一幕繁华。 你可愿转身落座,掌间朱砂,共我温酒煮茶。

Binder浅析

Binder是android上用于跨进程通信的通信机制。因此android开发的很多方面都与它挂钩。这也是一个学习的难点,网上也有很多教程,但基本上大多数要么简单介绍,要么从源码分析。从源码分析固然很好,但是源码是基于c++,并且实现很复杂,因而还是看不到。直到这周看到了Binder机制1---Binder原理介绍一文,终于能够初探门路,从整体上了解了Binder,因此有了本文。

一、为何是binder

虽然Android是基于linux的操作系统,但它却并没有采取linux常用的通信方式如socket,pipe, signal, trace等,下面简要说说它们的在移动平台的不足:

  • socket:是一个通用接口,导致其传输效率低,开销大;
  • 管道和消息队列:因为采用存储转发方式,所以至少需要拷贝2次数据,效率低;
  • 共享内存:虽然在传输时没有拷贝数据,但其控制机制复杂(比如跨进程通信时,需获取对方进程的pid,得多种机制协同操作)。

除此之外,binder带来了更多的优点:

  • 采用C/S的通信模式(linux只有socket采用C/S通信)
  • 安全性更高。Linux的IPC机制在本身的实现中,并没有安全措施,得依赖上层协议来进行安全控制。而Binder机制的 UID/PID是由Binder机制本身在内核空间添加身份标识,安全性高;并且Binder可以建立私有通道,这是linux的通信机制所无法实现的

二、binder的作用

  • 从IPC角度:Binder是android中跨进程的通信方式;
  • 从Android Framework角度:BinderServerManager(一下简称SM)连接各种Manager(ActivityManager,WindowManager等)和相应的ManagerServer的桥梁;
  • 从Android应用角度:Binder是客户端与服务端进行通信的媒介。当bindService的时候,服务端会返回服务端业务调用的Binder对象(引用),通过这个这个Binder对象,客户端可以获取服务端提供的服务或者数据。

三、引用和实体:

引用和实体:对于一个用于通信的实体(可以理解成具有真实空间的Object),可以有多个该实体的引用(没有真实空间,可以理解成实体的 一个链接,操作引用就会操作对应链接上的实体)。如果一个进程持有某个实体,其他进程也想操作该实体,最高效的做法是去获得该实体的引用,再去操作这个引用。

有些资料把实体称为本地对象,引用成为远程对象。可以这么理解:引用是从本地进程发送给其他进程来操作实体之用,所以有本地和远程对象之名。

IPC简单理解就是,进程A要请求进程B的服务,那么进程B是在自己的进程中完成服务或者拉取数据,然后将结果返回给进程A。所以很关键的一点要清楚我们的操作步骤中,哪些是要在进程A中执行,哪些要在进程B中执行。

四、Binder通信的整体框架

Binder整体框架由4部分组成:

  • 驱动程序
  • Client
  • Server
  • ServerManager(SM)

ClientServerSM分别在不同的进程,因此其实是涉及到三个进程间的通信。

SM相当于一个路由器,所有的Server都要在这里注册,然后Client就可已通过查询SM对应到具体的Server

主要分为四步:

  1. XXXServer在自己的进程中向Binder驱动申请创建一个XXXService的Binder的实体,存在于XXXServer进程中;
  2. Binder驱动为这个XXXService创建位于内核中的Binder实体节点以及Binder的引用,注意,是将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个名叫XXXService
  3. SM收到数据包后,从中取出XXXService名字和引用,填入一张查找表中
  4. 如果有ClientSM发送申请服务XXXService的请求,那么SM就可以在查找表中找到该Service的Binder引用,并把Binder引用(XXXBpBinder)返回给Client

《Binder机制1---Binder原理介绍》一文中还有提到SM初始化的问题,为何有问题呢?因为SM对于Server/Client来说也是一个服务(注册服务/查询服务),这也是跨进程通信,,那么此时Server/Client如何找到它所需要的注册/查询服务呢?看原文给出大答案:

如果把SM看作Server端,让它在Binder驱动一运行起来时就有自己的Binder实体(代码中设置ServiceManager的Binder 其handle值恒为0)。这个Binder实体没有名字也不需要注册,所有的client都认为handle值为0的binder引用是用来与SM通信 的(代码中是这么实现的),那么这个问题就解决了。那么,Client和Server中这么达成协议了(handle值为0的引用是专门与SM通信之用 的),还不行,还需要让SM有handle值为0的实体才算大功告成。怎么实现的呢?!当一个进程调用Binder驱动时,使用 BINDERSETCONTEXTMGR命令(在驱动的binderioctl中)将自己注册成SM时,Binder驱动会自动为它创建 Binder实体。这个Binder的引用对所有的Client都为0。

五、C/S通路的建立

ServerSM注册了Binder实体及其名字后,Client就可以通过Service的名字在SM的查找表中获得该Binder的引用了 (BpBinder)。Client也利用保留的handle值为0的引用向SM请求访问某个Service而获取引用。 SM就会从请求数据包中获得Service的名字,在查找表中找到该名字对应的条目,取出Binder的引用打包回复给Client;

Client也会自己的进程中向Binder驱动申请创建一个Binder实体,当向Server请求数据或服务时,就把自己的Binder的引用发送给Server,因此就建立了C/S通路:

  • 从client向Server发数据:Client为发送方,拥有Binder的实体;Server为接收方,拥有Binder的引用

  • 从server向client发数据:Server为发送方,拥有Binder的实体;client为接收方,拥有Binder的引用。

那么真实的数据是如何传递的呢?

发送方会通过Binder实体请求发送操作,会进行一下三步:

  1. Binder驱动会处理这个操作请求,把发送方的数据放入写缓存(binderwriteread.writebuffer) (对于接收方为读缓冲区),并把readsize(接收方读数据)置为数据大小;
  2. 接收方之前一直在阻塞状态中,当写缓存中有数据,则会读取数据,执行命令操作;
  3. 接收方执行完后,会把返回结果同样用bindertransactiondata结构体封装,写入写缓冲区(对于发送方,为读缓冲区)。

这样,双方都能接受数据和传递数据了。

最后加上对AIDL服务生成德源码简要解说一下,可以帮助我们进一步理解Binder以及它的使用。

六、一个AIDL服务的例子:

这部分主要是用一个夸进程获取书籍列表和添加书籍的例子,源码托管在我的Github上,地址为:https://github.com/cgspine/AIDLDemo

AIDL是Android Interface Definition Language的缩写,开发者可以以aidl的形式提供接口文件,而IDE会根据提供的文件生成相应的代码,简化开发者重复劳动  

目录结构

首先看一下Demo项目的目录结构,网上大多数教程基本上都是根据Eclipse讲解的,而Android Studio的操作有所不同。

如上图所示,在android studio中,aidl需要在app目录新建文件夹,包的根节点最好与java目录下得包根节点一致,如源码的com.example.cgspine.aidldemo,在android studio通过下图的方式,可以自动建立aidl文件夹和包得根目录

如果我们的AIDL接口需要访问到我们的model数据,我们需要在对于的包下建立同名的aidl文件,如demo中的Book.java,有对应的Book.aidl文件,并且Book类应该是可以序列化的对象。

我们可以通过执行Build -> Make Project 来生成相应的java类,其生成类的位置可见下图(需要切换为Project结构目录):

后文会有针对生成类的分析,帮助理解。

Model文件及序列化

首先我们需要我们的Model对象,因为需要夸进程传送数据,因此它需要是可序列化的,实现序列化也很简单,网上资料一大把,这里采用了andorid中常用的Parcelable实现序列化,源码如下:

public class Book implements Parcelable{

    private int mId;
    private String mName;

    public Book(int id, String name) {
        mId = id;
        mName = name;
    }

    public int getId() {
        return mId;
    }

    public void setId(int id) {
        mId = id;
    }

    public String getName() {
        return mName;
    }

    public void setName(String name) {
        mName = name;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mId);
        dest.writeString(mName);
    }

    private Book(Parcel in) {
        mId = in.readInt();
        mName = in.readString();
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}

建立AIDL文件

我建立了两个AIDL文件,代码如下:

// Book.aidl
package com.example.cgspine.aidldemo.model;

parcelable Book;


// IBookManager.aidl
package com.example.cgspine.aidldemo;

import com.example.cgspine.aidldemo.model.Book;//注意点:即使在同一个包下,也必须要显示import

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

Book.aidl是与我们的model对象Book.java相对应,然后IBookManager才能访问到BookIBookManager.aidl指出了两个方法getBookListaddBook,相信看名字就知道是做什么用的了。

AIDL自动生成类的源码解读

这部分对于我们理解和应用Binder很重要,如果你不懂,对于后面的开发也不会有太大的阻碍,但是如果想理解其运行机制,就有必要认真读一读源码了:

public interface IBookManager extends android.os.IInterface{
   /**
    * 关键的内部类,继承自Binder
    */   
   public static abstract class Stub extends android.os.Binder implements com.example.cgspine.aidldemo.IBookManager{

       /**
        * 标识符:完整的包名,用于识别Binder
        */
       private static final java.lang.String DESCRIPTOR = "com.example.cgspine.aidldemo.IBookManager";

       public Stub(){
           this.attachInterface(this, DESCRIPTOR);
       }
       /**
        * 将一个Binder转换为IBookManager类,用于生成IBookManager的实现类Proxy,可以分两种情况:
        * 
        * 1.Binder和调用者在同一进程,那么通过queryLocalInterface直接查询到对应的Proxy类,然后返回
        * 2.如果不在同一进程,那么就返回IBookManager的实现类Proxy
        */
       public static com.example.cgspine.aidldemo.IBookManager asInterface(android.os.IBinder obj){
           if ((obj==null)) {
               return null;
           }
           android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
           if (((iin!=null)&&(iin instanceof com.example.cgspine.aidldemo.IBookManager))) {
               return ((com.example.cgspine.aidldemo.IBookManager)iin);
           }
           return new com.example.cgspine.aidldemo.IBookManager.Stub.Proxy(obj);
       }
       @Override public android.os.IBinder asBinder(){
           return this;
       }

       /**
        * 这个方法运行在服务端,通过code来判断是调用哪一个方法
        */
       @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.example.cgspine.aidldemo.model.Book> _result = this.getBookList();
                   reply.writeNoException();
                   reply.writeTypedList(_result);
                   return true;
               }
               case TRANSACTION_addBook:{
                   data.enforceInterface(DESCRIPTOR);
                   com.example.cgspine.aidldemo.model.Book _arg0;
                   if ((0!=data.readInt())) {
                       _arg0 = com.example.cgspine.aidldemo.model.Book.CREATOR.createFromParcel(data);
                   }else {
                       _arg0 = null;
                   }
                   this.addBook(_arg0);
                   reply.writeNoException();
                   return true;
               }
           }
           return super.onTransact(code, data, reply, flags);
       }

       /**
        * 这个类在客户端实例化,是IBookManager在客户端的实现类
        * 实例化时会传入服务端Binder的引用,然后通过transact方法远程调用服务端对应的方法,并且接收服务端返回的数据
        */
       private static class Proxy implements com.example.cgspine.aidldemo.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.example.cgspine.aidldemo.model.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.example.cgspine.aidldemo.model.Book> _result;
               try {
                   _data.writeInterfaceToken(DESCRIPTOR);
                   mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                   _reply.readException();
                   _result = _reply.createTypedArrayList(com.example.cgspine.aidldemo.model.Book.CREATOR);
               }finally {
                   _reply.recycle();
                   _data.recycle();
               }
               return _result;
           }
           @Override 
           public void addBook(com.example.cgspine.aidldemo.model.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.example.cgspine.aidldemo.model.Book> getBookList() throws android.os.RemoteException;
   public void addBook(com.example.cgspine.aidldemo.model.Book book) throws android.os.RemoteException;

}

如果多读几遍源码,加上前文对Binder的分析,其思路还是很清晰的,我们可以总结一下:

  • IBookManager提供了一个继承自Binder``IBookManager并且实现了静态内部类Stub,我们在服务端可以实例化它,得到服务端Binder的实体
  • 服务端和客户端都会提供一个IBookManager的实现类,对于服务端,我们自己要去实现,而对于客户端,AIDL帮我们生成了,我们可以通过哦asInterface实例化它,即Proxy,其内部通过Binder实现与服务端的交互。(如果是同一进程,就没必要用Binder了)
  • 客户端动过binder调用transact进行RPC调用,然后服务端会调用onTransact来调用具体的方法。

整个过程关键是要区分服务端和客户端在各个阶段的角色。下面会继续我们的demo项目,从应用角度帮助大家理解

service

在demo中,我有一个BookService类,在manifest文件中注册时通过process指定其运行在不同的进程中:

<service android:name=".service.BookService"
    android:process=":service">
    <intent-filter>
        <action android:name="com.example.cgspine.aidldemo.bookservice" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

其源码如下:

public class BookService extends Service {

    private static final String TAG = "BookService";

    List<Book> mBookList = new ArrayList<>();

    /**
      * 服务端Binder的实例,同时也是IBookManager在服务端的实现类
      */
    private final IBookManager.Stub mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
           return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate");
        super.onCreate();
        /**
          * 初始化一些数据
          */
        mBookList.add(new Book(0, "《javascript权威指南》"));
        mBookList.add(new Book(1,"《javascript高级程序设计》"));
        mBookList.add(new Book(2,"《javascript框架设计》"));
        mBookList.add(new Book(3,"《javascript忍着秘籍》"));
        mBookList.add(new Book(4, "《javascript设计模式》"));
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind");
        return mBinder;
    }
}

Activity

Activity为我为我们的界面容器,承担着客户端的角色。其与RPC通信有两个关键点

首先需要的是一个ServiceConnection实例,可以通过它得到与服务端连接的状态以及Binder的引用。

private ServiceConnection mServiceConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG,"onServiceConnected");
        //通过asInterface方法得到客户端IBookManager的实现类Proxy的实例
        mBookManager = IBookManager.Stub.asInterface(service);
        mBookListAdapter.setBookList();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG,"onServiceDisconnected");
        mBookManager = null;
    }
};

其次是要建立客户端与服务端的联系,这可以通过bindService方法完成,在destory时记得调用unbindService

Intent intent = new Intent();
intent.setAction("com.example.cgspine.aidldemo.bookservice"); //action在manifest文件指定
bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);

下面是全部源码:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private IBookManager mBookManager;
    private MyAdaptor mBookListAdapter;


    @Bind(R.id.book_list) ListView mBookList;
    @Bind(R.id.book_num) EditText mBookNumEditText;
    @Bind(R.id.book_name) EditText mBookNameEditText;
    @Bind(R.id.book_submit) Button mBookSubmitBtn;

    private ServiceConnection mServiceConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG,"onServiceConnected");
            mBookManager = IBookManager.Stub.asInterface(service);
            mBookListAdapter.setBookList();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG,"onServiceDisconnected");
            mBookManager = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d(TAG, "bindService");
        Intent intent = new Intent();
        intent.setAction("com.example.cgspine.aidldemo.bookservice");
        bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);

        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        mBookListAdapter = new MyAdaptor();
        mBookList.setAdapter(mBookListAdapter);

        mBookSubmitBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String numStr = mBookNumEditText.getText().toString();
                String nameStr = mBookNameEditText.getText().toString();
                if(numStr == "" || nameStr == ""){
                    Toast.makeText(MainActivity.this,"编号和书名不能为空",Toast.LENGTH_SHORT).show();
                    return;
                }
                int num;
                try {
                    num = Integer.parseInt(numStr);
                } catch (NumberFormatException e) {
                    Toast.makeText(MainActivity.this,"编号必须为数字",Toast.LENGTH_SHORT).show();
                    return;
                }
                Book book = new Book(num,nameStr);
                try {
                    mBookManager.addBook(book);
                    mBookListAdapter.setBookList();
                } catch (RemoteException e) {
                    Toast.makeText(MainActivity.this,"添加过程出现错误",Toast.LENGTH_SHORT).show();
                    e.printStackTrace();
                }

            }
        });
    }

    @Override
    protected void onDestroy() {
        unbindService(mServiceConn);
        super.onDestroy();
    }

    class MyAdaptor extends BaseAdapter{
        List<Book> mBookList = new ArrayList<>();
        @Override
        public int getCount() {
            return mBookList.size();
        }

        @Override
        public Object getItem(int position) {
            return mBookList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if(convertView == null){
                convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.book_list_item,null);
                holder = new ViewHolder();
                holder.bookNum = (TextView) convertView.findViewById(R.id.book_item_num);
                holder.bookName = (TextView) convertView.findViewById(R.id.book_item_name);
                convertView.setTag(holder);
            }else{
                holder = (ViewHolder) convertView.getTag();
            }
            Book book = (Book) getItem(position);
            holder.bookNum.setText(""+book.getId());
            holder.bookName.setText(book.getName());
            return convertView;
        }

        public void setBookList() {
            try {
                mBookList = mBookManager.getBookList();
                notifyDataSetChanged();
            } catch (RemoteException e) {
            Toast.makeText(MainActivity.this,"获取书籍列表失败",Toast.LENGTH_SHORT).show();
            }
        }

        class ViewHolder{
            TextView bookNum;
            TextView bookName;
        }
    }

}

通过这篇文章,应该可以对binder的运行机制以及实际应用有了大概的了解,如果想要继续深入,那么就应该需要深入源码了。

参考:

←支付宝← →微信 →
comments powered by Disqus