Android Binder通信理解

550 阅读9分钟

首先附上一张Binder结构图

1.Binder通信的几大优势

安全性:传统的IPC通信无法获得对方可靠的UID/PID,通过实名Binder,可以极大提高安全性

性能:从图可以看出,只需一次拷贝即可实现进程间通信,极大减少了内核态和用户态之间切换的消耗

为什么不采用0次拷贝,0次拷贝就相当于共享内存,虽然开销小了但是难以管理

稳定性:Binder通信结构属于C/S模式,职责明确,稳定性较好

2.Binder的实现及通信过程

Binder的实现其实主要就是通过内存映射实现的,正常一次进程间通信会经历 用户态->内核态 再从内核态 ->用户态 经历两次拷贝,而Binder通信通过mmap() 在内核空间创建接收数据的

一次Binder通信的过程:

  1. 首先Binder驱动会在内核空间创建接收数据的缓存空间
  2. 然后在内核空间创建一个内核缓存区,Binder驱动会通过mmap()分别建立内核缓存区和数据接收缓存区的映射关系,以及数据接收缓存区和接收进程用户空间地址的映射关系
  3. 当发送方进程通过copyFromuser方法将消息从用户空间发送到内核缓存区,由于内核缓存区通过数据接收缓存区间接和接收方用户空间存在映射关系,这样就相当于将消息发送到了接收进程的用户空间,这样就完成了一次通信

3.Binder进程中的代理模式

当A进程中想要使用B进程中的对象是如何实现的呢?当A进程想要B进程中的object的时候,Binder不会返回一个真的object,他会创建一个和object一模一样的代理对象object proxy,他具有真的object的所有方法(实际只有外壳),但是当调用该proxy的方法时,实际上真正调用的是B进程中object内的方法,当Binder驱动收到A进程的消息后,发现是object proxy就去从自己维护的表单中查询真实的object,找到了进程B的object,去调用指定方法,并将返回值通过Binder驱动返回给A进程,这就像真的在通过object proxy进行方法调用,这样一次通信就完成了

4.Binder通信中相关类职责描述

IBinder:代表了跨进程通信的能力,只要实现了该接口就能实现跨进程通信(主要API是transact,用于和Binder对象进行通信)

IInterface:代表server进程对象具备什么样的能力 实际对应的就是AIDL文件中定义的接口

Binder:代表的是Binder本地对象,他的主要API是OnTransact方法

Stub:既继承了Binder对象同时实现了IInterface接口,继承了Binder说明他是一个Binder本地对象,实现了IInterface说明他具备了server端承诺给client端的能力;他是一个抽象类,具体的实现需要自行实现,它主要在service 内实现

Proxy:它是server端对象在client端的代理,他的构造函数是IBinder,在client端可以通过proxy进行操作,实际上他通过transact方法与server端进行通信

asInterface方法作用:

用于将服务端传来的Binder的对象转换成客户端需要的AIDL对象,在该方法内会区分进程(看client端和server端是否处在同一进程内)

asBinder方法

该方法主要返回当前的Binder对象(在stub内就返回自身,在Proxy内就返回remote对象)

5.Binder通信实现过程详解

5.1 不使用AIDL实现Binder通信

过程:首先app端通过创建serviceConneciton并调用bindService服务,开始绑定server端,server端会回调serviceConneciton的onServiceConnected方法,并传来IBinder类型的入参service,这个

这里通过一个简单的Demo进行Binder通信过程的梳理,这里的RemoteService作为服务端,clientActivity作为客户端进程

首先创建接口BookManager指明服务端可以提供的方法,他需要继承IInterface接口,表明服务端具备什么样的能力

/**
 * 定义服务端 RemoteService具备什么样的能力
 */
public interface BookManager extends IInterface {
    void addBook(Book book);
    List<Book> getBooks();
}

接下来如果要实现跨进程调用,需要实现一个跨进程调用对象Stub,它继承Binder,说明是一个本地对象(Server端),他是一个抽象类,具体的实现需要调用方自己实现(一般在service端实现)

public abstract class Stub extends Binder implements BookManager {
    ...
    
    public static BookManager asInterface(IBinder binder){
        if (binder == null)
            return null;
        //寻找Binder本地对象
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof BookManager){
            return (BookManager) iin;
        }
        return new Proxy(binder);
        /**
         * 如果server端(service所在的进程)和client端处于同一个进程 那么此时不需要跨进程通信 直接调用Stub的方法即可(Stub实例化时已经实现了具体的方法)
         * 反之,则返回proxy对象  从构造方法看 该对象持有着远程Binder引用  如果调用Stub.proxy接口 那么将会是IPC调用  即通过transact方法与服务端进行通信
         * 从字面意思看stub是服务端实现的存根(Stub工作在server进程),
         * proxy则是stub的代理 proxy工作在client进程
         */
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    /**
     * Binder的主要API是onTransact方法   IBinder的主要API是transact方法
     */
    @Override
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        switch (code){
            ...
            case TRANSAVTION_addBook:
                data.enforceInterface(DESCRIPTOR);
                Book arg0 = null;
                if (data.readInt() !=0){
                    arg0 = Book.CREATOR.createFromParcel(data); //将流数据转换成book对象
                }
                this.addBook(arg0);
                reply.writeNoException();
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }
}

个人理解Stub的作用是提供server端Binder通信的能力,client端通过server端的Stub进行通信,不用直接修改service端的相关逻辑,他的主要功能就是处理跨进程通信,主要包括两方面:asInterface和onTransact,

asInterface: 当client端通过bindService和server端建立连接时,他会创建一个ServiceConnection作为入参传递进去,在连接成功后会回调他的serviceConnected方法,我们一般在这个方法内通过 asInterface(IBinder binder) 创建Bookmanager,这里的入参binder是驱动传来的。

该方法会根据调用方是否和server处于同一进程返回不同对象,如果处于同一进程那么直接将IBinder对象转为BookManager对象并返回,client直接进行调用;如果不处于同一进程,那么创建Proxy对象,他的构造函数就是IBinder对象,表面上client端通过proxy进行方法调用,实际上他通过transact方法真正发生作用的是server端的Stub的方法

onTransact方法主要用来回应proxy端的transact方法,会在该方法内直接调用this.addBook方法(因为stub端已经实例化了该虚拟类并实现了指定的方法)

接下来看下Proxy端的实现

public class Proxy implements BookManager {
    private IBinder remote;

    public Proxy(IBinder remote) {
        this.remote = remote;   //传入IBinder 进行远程调用
    }

    //代理类 该方法内并没有真正的实现
    @Override
    public void addBook(Book book) {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();

        try {
            data.writeInterfaceToken(DESCRIPTOR);
            if (book!= null){
                data.writeInt(1);
                book.writeToParcel(data,0);  //通过Parcel进行数据传输
            } else {
                data.writeInt(0);
            }
            remote.transact(Stub.TRANSAVTION_addBook,data,reply,0);  //Binder驱动唤醒server进程,调用server进程的本地对象Stub的onTransact方法
            reply.readException();
        } catch (RemoteException e) {
            e.printStackTrace();
        }finally {
            reply.recycle();
            data.recycle();
        }
    }
    ...

    @Override
    public IBinder asBinder() {
        return remote;
    }
}

Stub类中的addBook是个抽象方法,需要在service中进行实例化并实现相关方法

  • 如果client和server处在同一个进程,那么直接调用Stub的方法
  • 如果不处于同一个进程,那么client端调用proxy对象的addBook方法,然后通过transact方法与server端的stub进行通信,stub在onTransact方法中进行处理相关逻辑

我们看下service的实现

public class RemoteService extends Service {
    ...
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return bookManager;
    }
    
   private final Stub bookManager = new Stub() {  //如果server端和client是同一进程  传递的就是该stub对象 否则就是该stub的proxy对象
       @Override
       public void addBook(Book book) {
           synchronized (this) {  //注意这里使用同步操作  因为可能有多个client进程进行操作
               if (books == null) {
                   books = new ArrayList<>();
               }

               if (book == null){
                   return;
               }
               book.setPrice(book.getPrice() * 2);
               books.add(book);

               Log.e("Server", "books: " + book.toString());
           }
       }
        ...
}

在service类里面实例化Stub并实现IInterface中的相关方法

最后看下client端是如何进行调用的

public class Activity_binder extends AppCompatActivity {
    private BookManager bookManager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder);

        Button btn = findViewById(R.id.btn_binder);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                attemptBinderService();
                try {
                    Book book = new Book();
                    book.setPrice(112);
                    book.setName("i am chinese");
                    bookManager.addBook(book);
                    Log.d("ClientActivity", bookManager.getBooks().toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        });
    }

    private void attemptBinderService() {
        Intent intent = new Intent(this, RemoteService.class);
        intent.setAction("cn.xiaomi.apptrain.server");
        //Intent 即指向具体要连接的类
        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
    }

    //该类的作用是当service开始或者结束的时候通过回调进行相关逻辑
    private ServiceConnection serviceConnection = new ServiceConnection(){ 
        //当建立连接时  会回调该方法
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //将server端传来的IBinder转成bookManager对象 如果server端和client不在同一个进程 那么此处的bookManager指的是Proxy对象
            //这里的service是一个BinderProxy对象
            bookManager = Stub.asInterface(service);   //获得server端在client端的引用bookManager
            if (bookManager != null){
                try {
                    List<Book> books = bookManager.getBooks();
                    Log.d("ClientActivity", books.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        attemptBinderService();  //建立链接
    }
}

这里在client端start的回调方法中与service端建立连接,通过bindService方法建立连接时需要传入ServiceConnection对象,并在该对象的onServiceConnected回调方法中实例化BookManager对象。

这里传入的IBinder是BinderProxy对象,它是远程进程的Binder对象的本地代理

到这里,一次跨进程通信就完成了。

5.2通过AIDL实现Binder通信

正常使用时,直接通过AIDL文件就能简化很多操作,它会自动帮我们生成Stub、Proxy文件

我们以UserManager相关类来展开梳理

首先创建AIDL文件,里面包含着server提供的功能接口

(创建完之后需要make project 一下,然后AS会自动帮我们生成相关类 类路径build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/cn.xiaomi.apptrain.server)

interface IUserManager {
    void addUser();
}

下面就是自动生成的文件IUserManager

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package cn.xiaomi.apptrain.server;
// Declare any non-default types here with import statements

public interface IUserManager extends android.os.IInterface
{
  /** Default implementation for IUserManager. */
  public static class Default implements cn.xiaomi.apptrain.server.IUserManager
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public void addUser() throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements cn.xiaomi.apptrain.server.IUserManager
  {
    private static final java.lang.String DESCRIPTOR = "cn.xiaomi.apptrain.server.IUserManager";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an cn.xiaomi.apptrain.server.IUserManager interface,
     * generating a proxy if needed.
     */
    public static cn.xiaomi.apptrain.server.IUserManager asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof cn.xiaomi.apptrain.server.IUserManager))) {
        return ((cn.xiaomi.apptrain.server.IUserManager)iin);
      }
      return new cn.xiaomi.apptrain.server.IUserManager.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
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_addUser:
        {
          data.enforceInterface(descriptor);
          this.addUser();
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements cn.xiaomi.apptrain.server.IUserManager
    {
      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;
      }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in AIDL.
           */
      @Override public void addUser() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().addUser();
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static cn.xiaomi.apptrain.server.IUserManager sDefaultImpl;
    }
    static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(cn.xiaomi.apptrain.server.IUserManager impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static cn.xiaomi.apptrain.server.IUserManager getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public void addUser() throws android.os.RemoteException;
}

然后直接创建server端的UserManagerService

public class UserManagerService extends Service {
    private static final String TAG = "UserManagerService";

    public UserManagerService() {
    }
     //直接
     private Binder binder = new IUserManager.Stub() {
         @Override
         public void addUser() throws RemoteException {
             Log.d(TAG,"add user");
         }
     };
    @Override
    public IBinder onBind(Intent intent) {
        return binder;   //serviceConnected中的入参IBinder service就是该binder的引用BinderProxy
    }
}

然后在client端使用时方法同之前的一致,同样通过bindService进行服务绑定,并传入serviceConnection

private ServiceConnection serviceConnection2 = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
         userManager = IUserManager.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
           ...
    }
};

到这里,就能通过AIDL完成一次跨进程通信

这里主要参考了zhuanlan.zhihu.com/p/35519585 文章,然后自己做了下总结以及加入了些个人的理解。

如有错误,欢迎指正~!