Android IPC | AIDL详解

2,623 阅读17分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

前言

前面文章我们重点分析了Linux的进程划分,以及为了减少一次拷贝的内存映射技术,本篇文章我们就来介绍Android中使用最多也最具有代表性的IPC方式:AIDL。

本篇文章主要侧重AIDL的使用细节,然后重点分析AIDL生成的文件,通过手写AIDL生成文件来大致理解Binder机制,话不多说,直接开整。

正文

我们先来看看AIDL的使用。

AIDL使用

AIDL的原理是通过Binder来实现,而且是C/S架构,所以AIDL分为服务端和客户端2个方面来使用:

  1. 服务端:服务端首先需要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口。

从这个简单描述我们知道,作为服务端必须建立在Service上,这也非常好理解,Service作为四大组件之一,由Android系统统一管理,同时自己就可以被其他进程所启动;

  1. 客户端:客户端首先就需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL接口中的方法了。

从客户端实现我们可知这里的关键就是Binder了,本篇文章后面我们在使用AIDL时会简单说明一些Binder相关类的作用。

为了更好地说明AIDL使用的细节,这里我使用俩个APP交互的方式,而不是一个APP开启多进程。

AIDL接口的创建

AIDL接口的的创建非常重要,首先是需要定义在不同进程之间传递的自定义类型,比如我这里定义为Book:

//包名
package com.zyh.ipc;

import android.os.Parcel;
import android.os.Parcelable;

//必须实现Parcelable接口
public class Book implements Parcelable {
    public int BookId;
    public String BookName;

    ...省略
}

这是一个Book.java类,必须实现Parcelable接口,然后就是存放该类的目录,这里推荐把涉及到AIDL通信的自定义类都放到一个相同的目录中,这是因为俩个进行跨进程通信的APP会对自定义的类型对象进行序列化和反序列化,而这种序列化方式会记录类的信息,即使俩个APP中都定义了Book类,而且成员变量都一样,但是因为包名不一样,同样是无法反序列化的。

定义完自定义类型后,凡是在AIDL文件中用到的自定义Parcelable类型,都必须新建一个和它同名的AIDL文件,并且在其中声明它为Parcelable类型,而AIDL文件的目录,可以使用AS直接创建出来,这里在AIDL目录下创建一个Book.aidl:

image.png

//Book.aidl
package com.zyh.ipc;

parcelable Book;

再然后我们就可以定义AIDL接口了,该接口就是定义IPC通信的内容,注意这个接口和Java接口有一点不一样,它是只支持方法的,不支持声明静态常量,在本例中创建管理Book的接口IBookManager.aidl:

//包名
package com.zyh.ipc;
//必须手动导入自定义的Parcelable类型
import com.zyh.ipc.Book;
//接口
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

这里需要注意俩点:

  1. 就是前面定义的自定义Parcelable类型,在AIDL文件中必须手动导入
  2. 在AIDL文件中,并不是所有的数据类型都可以使用的,AIDL文件支持的数据类型如下:
  • 基本数据类型,包括int、long、char、boolean等;
  • String和CharSequence;
  • List只支持ArrayList,里面元素必须能被AIDL支持;
  • Map只支持HashMap,里面的key和value的类型也必须被AIDL支持;
  • Parcelable,即实现了Parcelable接口的对象。

创建完AIDL文件以及AIDL用到的自定义Parcelable类型,这时就需要把这些文件从服务端APP拷贝一份到客户端APP中,注意类的包名千万不要改变,当一端的类结构发生改变,在另一端必须替换为一样的。

服务端的实现

在定义完AIDL接口后,需要Build一下项目,这时在app/build/generated/aidl_source_out_dir/debug目录下会生成一个和AIDL接口同名的java文件,即IBookManager.java,关于这个文件我们后面会仔细分析。

这里先来看一下服务端如何实现,前面说了需要通过Service,直接定义一个服务BookManagerService:

class BookManagerService : Service() {

    //服务端的书籍列表,这里需要使用线程安全的类来保存
    private val mBookList = CopyOnWriteArrayList<Book>()

    //实现接口的Binder
    private val mBinder = object : IBookManager.Stub(){
        override fun getBookList(): MutableList<Book> {
            return mBookList
        }

        override fun addBook(book: Book?) {
            mBookList.add(book!!)
        }
    }

    //onBind回调中返回我们定义的Binder
    override fun onBind(intent: Intent): IBinder {
        return mBinder
    }

    //在服务创建时,往Book列表中添加俩本书
    override fun onCreate() {
        super.onCreate()
        mBookList.add(Book(11,"Android"))
        mBookList.add(Book(22,"Ios"))
    }
}

这个类涉及的知识点比较多,我们来挨个分析:

  1. 保存Book的列表为什么要使用CopyOnWriteArrayList这个线程安全的类型,这是因为getBookList()和addBook()这俩个方法会在线程池中运行,当有多个客户端绑定该服务,同时调用这俩个方法,使用线程安全的集和可以保证数据正确。

类似的集和还有ConcurrentHashMap线程安全的集和。

  1. 这个onBind方法返回的类型是IBinder,这里的IBinder是一个接口,可以这么简单理解:所有通过Binder机制进行IPC的对象,都必须实现该类

这里mBinder是IBookManager.Stud类的实例,这个类是AIDL文件生成的文件,该类定义如下:

public static abstract class Stub extends android.os.Binder implements com.zyh.ipc.IBookManager

可以发现它继承至Binder,这个Binder就是IBinder的实现类,同时实现了IBookManager接口,根据IBinder接口的特性,这里我们就可以认为这个mBinder就具有了跨进程的能力。

客户端的实现

前面既然说了对象实现了IBinder就有了跨进程的能力,那可以想象在客户端应该就是拿到刚刚在服务端定义的Binder对象,然后对这个Binder进行操作,所以客户端代码如下:

//通过bindService连接客户端APP的服务
findViewById<Button>(R.id.bindService).setOnClickListener {
    bindService(
        //这里的Intent除了需要action,还必须设置服务端APP的包名
        Intent("com.zyh.ipc.BookManagerService").setPackage("com.zyh.ipc"),
        conn,
        BIND_AUTO_CREATE
    )
}
//服务连接成功和断开的回调
private val conn = object : ServiceConnection{

    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        Log.i("ipc", "onServiceConnected: ")
        //通过asInterface方法把IBinder对象转成IBookManager类型
        bookManager = IBookManager.Stub.asInterface(service)
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        Log.i("ipc", "onServiceDisconnected: ")
    }

}
//查看书籍列表
findViewById<Button>(R.id.getBook).setOnClickListener {
    val list = bookManager?.bookList
    //看一下这个list的类型
    Log.i("ipc", "onCreate: ${list?.javaClass?.name}")
    for (book in list!!) {
        Log.i("ipc", "onServiceConnected: ${book.BookId} ${book.BookName}")
    }
}

这里的核心代码就是IBinder对象的asInterface了,这里可以看成就获取到了服务端的Binder了(真实原理我们后面再说)。

我们看一下打印:

2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onCreate: java.util.ArrayList
2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onServiceConnected: 11 Android
2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onServiceConnected: 22 Ios

这里会发现list的类型是ArrayList,为什么说这个呢 原因是刚开始我们说AIDL支持List,但是只支持ArrayList,但是我们在服务端代码中却用了CopyOnWriteArrayList,这个类定义:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

会发现它虽然是名字和ArrayList相关,但是它却不是继承至ArrayList,这就说明了数据在传输过程中,Binder对数据类型做了处理。

这里还有注意一点,就是客户端调用完getBookList方法后,会挂起当前线程,直到等待结果返回,是一个同步方法,所以当在服务端该方法执行时间过长,在主线程调用该方法可能会导致ANR。

这里我们可以做个小小总结:

  1. 服务端和客户端使用同样的AIDL文件,会生成同样的类,而生成类IBookManager.Stub是继承至Binder,且实现了IBookManager接口。
  2. 服务端的作用就是实现IBookManager.Stub接口,实现业务需求。
  3. 客户端通过拿到Binder对象,然后转成IBookManager类型,和服务端进行通信。

使用回调

在前面我们已经说了AIDL的简单使用了,现在我们加个功能,就是在AIDL中使用回调

这个功能在开发车机、电视等系统中经常用,比如导航APP在运行时,一般需要在Launcher显示出导航的简短信息,这个导航实时信息就是导航APP通过回调给的。

在本章例子中,我们添加一个回调监听,希望服务端每隔一段时间能主动回调当前Book的信息。首先我们定义接口,这个接口也必须定义为aidl类型:

// IOnBookListener.aidl
package com.zyh.ipc;

import com.zyh.ipc.Book;

//返回所有Book
interface IOnBookListener {

    void onAllBook(in List<Book> books);

}

这里为什么要定义为AIDL接口,因为接口中的方法是需要跨进程调用的,方法回调的数据是需要序列化和反序列化的,所以一般的Java接口肯定满足不了

定义完新接口后,需要在原来接口中添加注册和反注册的方法:

package com.zyh.ipc;

//必须手动导入
import com.zyh.ipc.Book;
import com.zyh.ipc.IOnBookListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    //添加监听
    void registerListener(IOnBookListener listener);
    //移除监听
    void unregisterListener(IOnBookListener listener);
}

修改完AIDL接口,需要Build一下项目,然后在服务端进行简单修改:

class BookManagerService : Service() {

    private val mBookList = CopyOnWriteArrayList<Book>()

    //listener列表
    private val mListenerList = CopyOnWriteArrayList<IOnBookListener>()

    private val mBinder = object : IBookManager.Stub(){
        override fun getBookList(): MutableList<Book> {
            return mBookList
        }

        override fun addBook(book: Book?) {
            mBookList.add(book!!)
        }

        override fun registerListener(listener: IOnBookListener?) {
            if (mListenerList.contains(listener)){
                Log.i("BMS", "registerListener: already exist")
            }else{
                mListenerList.add(listener)
            }
        }

        override fun unregisterListener(listener: IOnBookListener?) {
            if (mListenerList.contains(listener)){
                mListenerList.remove(listener)
            }else{
                Log.i("BMS", "unregisterListener: not exist")
            }
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return mBinder
    }

    override fun onCreate() {
        super.onCreate()
        mBookList.add(Book(11,"Android"))
        mBookList.add(Book(22,"Ios"))
        //开启线程循环回调book信息
        Thread{
            while (true){
                Thread.sleep(5000)
                for (listener in mListenerList){
                    Log.i("BMS", "onCreate: 回调信息")
                    listener.onAllBook(mBookList)
                }
            }
        }.start()
    }
}

上面代码非常简单,和保存Book一样也新建一个listener列表,然后在onCreate方法中,每隔5s回调一次Book信息,同时对Binder实现类添加了注册和反注册的功能。

写完服务端代码,必须把修改涉及的文件拷贝到客户端,这一步千万不能出错,在客户端中一样先Build,然后在服务连接成功后,创建监听的实例,给注册到服务端:

private val conn = object : ServiceConnection{

    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        Log.i("ipc", "onServiceConnected: ")
        bookManager = IBookManager.Stub.asInterface(service)
        //这里注意必须是创建IOnBookListener.Stub的实例
        bookManager?.registerListener(object : IOnBookListener.Stub(){
            override fun onAllBook(books: MutableList<Book>?) {
                for (book in books!!) {
                    Log.i("ipc", "onAllBook: ${book.BookId} ${book.BookName}")
                }
            }
        })
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        Log.i("ipc", "onServiceDisconnected: ")
    }

}

这里要注意的上面已经注释了,就是在跨进程中,我们定义监听的接口必须是aidl类型的,而非普通的Java类型,所以客户端在注册时必须是传递生成类Stub的对象,至于原因后面会细说;这里创建了IOnBookListener.Stub的实例,但是通过跨进程后,客户端实例和服务端实例将不是同一个,上面代码的运行如下:

image.png

可以发现可以正常实现功能,每5S回调一次服务的Book列表信息。

既然我们可以注册listener,当然也需要测试一下反注册listener,但是在反注册listener中,我们会发现如下打印:

image.png

服务端居然提醒找不到该listener,并且客户端还是一直在回调,这不是大坑吗 这就是AIDL使用回调时需要注意的点。

我们在客户端新建listener对象,通过Binder传递到服务端后,会生成一个全新的对象,这是因为IPC的本质是序列化和反序列化,对象是需要序列化后才可以传输,所以这个listener在客户端和服务端俩个进程就不是一个对象。

既然不是同一个对象,那回调功能咋是正常的呢,这个后面分析AIDL生成文件时细说,这里先解决如何正常反注册。

解决的办法非常简单,使用RemoteCallbackList来代替前面的CopyOnWriteArrayList集和,从名字就可以看出这个是专门用来存放远程回调的,我们可以简单看一下该类的定义:

public class RemoteCallbackList<E extends IInterface>

可以发现它是一个泛型类,而泛型类型是IInterface,这个接口很重要,定义如下:

 //Binder接口的基类,当我们定义一个跨进程的接口时,必须继承这个接口
public interface IInterface
{
     //由于跨进程的真实原理是通过Binder,所以通过这个把接口和Binder对象联系起来。
     //我们把接口转成Binder要用这个,而不是直接强转。
    public IBinder asBinder();
}

RemoteCallbackList原理非常简单,在它的内部有一个Map结构专门用来保存所有AIDL回调,这个Map的key是IBinder类型,value是Callback类型

ArrayMap<IBinder, Callback> mCallbacks
        = new ArrayMap<IBinder, Callback>();

而当我们注册一个回调时,其中的key和value是如下方法获取:

IBinder binder = callback.asBinder();
Callback cb = new Callback(callback, cookie);
unregister(callback);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);

到这里我们就明白了,虽然说跨进程传输中,在客户端新建的对象和服务端不是同一个,但是这俩个对象对应的Binder对象是同一个,所以当注册和反注册时可以针对对应的Binder来进行增删。

同时RemoteCallbackList还有一个很有用的功能,就是当客户端进程终止后,它能自动移除客户端注册的listener,而且RemoteCallbackList内部自动实现了线程同步功能,所以我们使用它来注册和反注册时,不需要做额外的线程同步工作。

重连机制

为了程序的健壮性,当服务端进程意外停止时,我们需要重新连接服务。在这里重新连接服务,有俩种方法:

  1. 可以给在服务端运行的Binder设置DeathRecipient监听回调,当Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务。

代码如下:

private val deathRecipient = object : IBinder.DeathRecipient {
    override fun binderDied() {
        Log.i("ipc", "binderDied: 调用 需用重连Service")
    }
}

定义完deathRecipient后,在bindService成功的回调中给binder添加该死亡监听即可:

override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
    Log.i("ipc", "onServiceConnected: ")
    bookManager = IBookManager.Stub.asInterface(service)
    //注册Binder死亡回调
    service?.linkToDeath(deathRecipient, 0)
    bookManager?.registerListener(listener)
}
  1. 通过bindService的回调,即ServiceConnection,可以在其中的onServiceDisconnected方法中进行重连机制,代码如下:
override fun onServiceDisconnected(name: ComponentName?) {
    Log.i("ipc", "onServiceDisconnected: ")
    //需要进行重连
}

到这里,AIDL的使用我们基本就说完了。其实AIDL文件是一套java代码生成规则,可以看成是给编译器来使用的一套规则,通过AIDL文件,编译器生成符合跨进程通信的java代码。 这样的好处就是不用让开发者编写复杂的跨进程代码。

AIDL生成文件分析

会使用AIDL还是不够的,必须要能看得懂AIDL文件所生成的java文件,因为在很多源码中,都是直接使用java代码来编码的。

前面说了当IPC时我们需要定义.aidl接口,这个接口就是IPC的内容,当定义了一个IBookManager.aidl接口,这个接口肯定不能直接起作用,它会生成一个IBookManager.java,代码如下:

package com.zyh.ipc;
//IBookManager是一个接口,继承至IInterface
public interface IBookManager extends android.os.IInterface
{
  //对IBookManager接口的默认实现
  public static class Default implements com.zyh.ipc.IBookManager
  {
    @Override public java.util.List<com.zyh.ipc.Book> getBookList() throws android.os.RemoteException
    {
      return null;
    }
    @Override public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  //IPC通信实现的存根类,这里实现了IBookManager接口,同时继承了Binder
  public static abstract class Stub extends android.os.Binder implements com.zyh.ipc.IBookManager
  {
      //唯一描述符
    private static final java.lang.String DESCRIPTOR = "com.zyh.ipc.IBookManager";
       //构造函数,关联接口
    public Stub()
    {
            //Binder的方法,将特定的接口与Binder关联。当调用完该方法后,queryLocalInterface方法将会根据唯一描述符返回特定的接口
      this.attachInterface(this, DESCRIPTOR);
    }
 
     //把一个IBinder对象转换成IBookManager对象,如果是跨进程通信则需要生成一个代理类
    public static com.zyh.ipc.IBookManager asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      //通过唯一描述符,找到跨进程的接口对象
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      //如果跨进程的类型是IBookManager,说明是没有跨进程的
      if (((iin!=null)&&(iin instanceof com.zyh.ipc.IBookManager))) {
        return ((com.zyh.ipc.IBookManager)iin);
      }
      //否则返回Stub.Proxy类型对象
      return new com.zyh.ipc.IBookManager.Stub.Proxy(obj);
    }
    
    //这个是IInterface接口的方法,即需要把该接口强转为Binder对象
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    
    //交换的核心代码,返回值返回true则表示调用成功。
    //code表示唯一标识符,即该调用哪个方法
    //data表示客户端发送给服务端的数据
    //reply表示服务端返回的数据
    @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;
        }
        //获取book列表
        case TRANSACTION_getBookList:
        {
            //验证当前接口是否匹配
          data.enforceInterface(descriptor);
             //这个this表示IBookManager接口的方法
          java.util.List<com.zyh.ipc.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        case TRANSACTION_addBook:
        {
            //验证当前接口是否匹配
          data.enforceInterface(descriptor);
          com.zyh.ipc.Book _arg0;
          if ((0!=data.readInt())) {
              //data是Parcel数据,这里根据CREATOR创建出Book对象
            _arg0 = com.zyh.ipc.Book.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          //同样是调用IBookManager接口的方法
          this.addBook(_arg0);
          reply.writeNoException();
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    
    //IBookManager接口的代理类,当真进行IPC通信时,调用该类
    private static class Proxy implements com.zyh.ipc.IBookManager
    {
      private android.os.IBinder mRemote;
      
      //远程的IBinder对象
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      
      //IInterface接口方法
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      
      
      @Override public java.util.List<com.zyh.ipc.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.zyh.ipc.Book> _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          //调用刚刚定义的transact方法,会回调Stub类中的onTransact方法
          boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
          //当调用不成功且默认实现不为空时,即是非IPC通信
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getBookList();
          }
          _reply.readException();
          _result = _reply.createTypedArrayList(com.zyh.ipc.Book.CREATOR);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      @Override public void addBook(com.zyh.ipc.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);
          }
          boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().addBook(book);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.zyh.ipc.IBookManager sDefaultImpl;
    }
    
    //静态变量,每个方法对应一个静态变量
    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 static boolean setDefaultImpl(com.zyh.ipc.IBookManager impl) {
      //当客户端和服务端在同一个进程中会被调用
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.zyh.ipc.IBookManager getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  //java文件的接口
  public java.util.List<com.zyh.ipc.Book> getBookList() throws android.os.RemoteException;
  //java文件的接口
  public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException;
}

上面生成的代码还是蛮多的,看起来有点乱的原因是定义了太多内部类,首先就是IBookManager.java这个类,它继承了IInterface接口,同时它自己也是一个接口,所有可以在Binder中传输的接口都必须继承至IInterface接口

同时在IBookManager.java中定义了俩个方法,这俩个方法就是我们在aidl中定义的方法,是IPC希望实现的功能接口。同时定义了俩个静态整形的id分别用来标识这俩个方法,这个俩个id用于标识在transact过程中客户端所请求的到底是哪个方法

然后是IBookManager.java中的第一个内部类:Default,该类是默认实现,可以无需关注。

IBookManager.java中第二个内部类:Stub,其中Stub的意思是"存根",表示一些接口的实现类。该类是核心,它首先是继承至Binder,同时实现了IBookManager接口,下面详细列举一下该类中的实现细节:

  1. DESCRIPTOR,这个是Binder的唯一表示,一般用当前Binder的类名表示。
  2. asInterface,用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换是区分进程的,如果客户端和服务端在同一个进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是封装后的Stub.proxy对象。
  3. asBinder,用于返回当前Binder对象。
  4. onTransact,该方法会运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由该方法来处理。

该方法的参数非常重要,服务端通过code可以确定客户端所请求的目标方法是什么,然后从data中取出目标方法所需要的参数,然后执行目标方法,当目标方法执行完毕后,会向reply中写入返回值。这里注意,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们不希望随便一个进程都能远程调用服务。

  1. Proxy类,该类是Stub的内部类,当客户端和服务端不在一个线程时,asInterface就会创建Proxy类的对象,然后构造函数传入的IBinder对象还是服务端的Binder对象。
  2. Proxy#getBookList,这个方法运行在客户端中,当客户端调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List,然后把该方法的参数信息写入_data中,接着调用transact方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并且从_reply中取出RPC过程返回的结果。

上面便是AIDL的生成文件的简单分析。

手写一个Binder使用实例

在前面我们也说了.aidl文件只是生成Java代码的规则,而且这里我们也分析了AIDL生成的文件,所以我们就可以不用AIDL来手动创建Binder对象,这样一样可以实现IPC通信。

  1. 先声明一个跨进程的接口,该接口只需要继承IInterface接口即可,IInterface有一个asBinder方法,这个接口定义如下:
public interface ICustomBookManager extends IInterface {

    static final java.lang.String DESCRIPTOR = "com.zyh.ipc.IBookManager";

    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.zyh.ipc.Book> getBookList() throws android.os.RemoteException;
    
    public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException;
}

这里定义了ICustomBookManager的Java接口,并且定义了俩个方法以及方法的唯一表示符号。

  1. 然后是新建实现该接口的Java类,同时继承至Binder:
//继承至Binder,同时实现ICustomBookManager接口
public class CustomBookManagerImpl extends Binder implements ICustomBookManager {

    public CustomBookManagerImpl() {
        this.attachInterface(this, DESCRIPTOR);
    }

    //必须的方法,把IBinder对象转成接口类型
    public static ICustomBookManager asInterface(IBinder obj){
        if ((obj==null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin!=null)&&(iin instanceof com.zyh.ipc.CustomBookManagerImpl))) {
            return ((com.zyh.ipc.CustomBookManagerImpl)iin);
        }
        return new com.zyh.ipc.Proxy(obj);
    }

    //模板代码,在服务端被调用
    @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_getBookList:
            {
                data.enforceInterface(descriptor);
                java.util.List<com.zyh.ipc.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
            case TRANSACTION_addBook:
            {
                data.enforceInterface(descriptor);
                com.zyh.ipc.Book _arg0;
                if ((0!=data.readInt())) {
                    _arg0 = com.zyh.ipc.Book.CREATOR.createFromParcel(data);
                }
                else {
                    _arg0 = null;
                }
                this.addBook(_arg0);
                reply.writeNoException();
                return true;
            }
            default:
            {
                return super.onTransact(code, data, reply, flags);
            }
        }
    }

    @Override
    public List<Book> getBookList() throws RemoteException {
        return null;
    }

    @Override
    public void addBook(Book book) throws RemoteException {

    }

    //返回Binder
    @Override
    public IBinder asBinder() {
        return this;
    }
}
  1. 在上面代码我们知道当是真的IPC时,是需要一个代理类的,由代理类来进行数据转换和接口请求
public class Proxy implements ICustomBookManager {

    private android.os.IBinder mRemote;
    Proxy(android.os.IBinder remote)
    {
        mRemote = remote;
    }

    @Override
    public List<Book> getBookList() throws RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.zyh.ipc.Book> _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_getBookList, _data, _reply, 0);
            _reply.readException();
            _result = _reply.createTypedArrayList(com.zyh.ipc.Book.CREATOR);
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

    @Override
    public void addBook(Book book) throws 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);
            }
            boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_addBook, _data, _reply, 0);
            _reply.readException();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
    }

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

可以发现上面代码和生成的代码几乎一样,只不过可读性多了很多,里面关键的地方可以归纳如下几点:

  1. 跨进程的接口必须要继承IInterface接口
  2. 接口的Java实现类必须继承至Binder
  3. asInterface必不可少,可以把Binder对象转成接口类型,这里由于要区分是否是跨进程,如果是跨进程需要调用Proxy代理类来辅助。
  4. onTransact是调用transact后回调,是服务端处理请求,在线程池中执行
  5. Proxy代理类就是用来调用接口,以及方法相关参数的序列化与反序列化过程

总结

不知不觉本篇文章的子树已经非常多了,内容较多,下面做个总结:

  1. 在AIDL使用篇,主要就是AIDL文件的写法要尤其注意,以及使用回调和重连机制的使用。
  2. 在AIDL生成文件分析中,我们要明确看出其不同内部类的作用。
  3. 在手写Binder通信时,我们需要知道IInterface的含义,以及实现接口和继承至Binder的Java类。
  4. 其中要会根据是否跨进程来选择是否使用代理类,其中onTransact是在服务端线程池中调用,而Proxy中的方法是同步执行,会挂起。

笔者水平有限,如有问题,欢迎大家评论指正。