第二章 IPC 机制

2.1 Android IPC 简介

IPC 是 Inter-Process Communication 的缩写,含义为进程间通信或跨进程通信。在计算机中,两个进程是运行在两个内存空间的,其通信不想线程间通信那样可以直接通过内存通信(持有对象,静态对象)。

IPC 并非安卓独有,几乎所有操作系统都会提供自己的 IPC 方式,例如通道等。这里主要介绍 Android 的 IPC。

2.2 Android 中多进程

需要用到多进程的场景其实不是很多,但有一个重要的场景。Android 对于单个进程最多可使用的内存是有限制的,早期的一些版本可能是 16MB,因此,如果你想要你的程序能获取更大的内存,则只能同个多进程的方式实现。

2.2.1 开启多进程模式

在 Android 中常规方式使用多进程只有一种方法,那就是在四大组件 —— Activity, Service, Receiver, ContentProvider 注册的时候指定 android:process 参数,这也意味着新的进程必须依赖于某个四大组件。当然除了常规方式,你也可以同个 JNI 在 native 层去开启新进程,这里不做讨论。

<activity android:name=".MainActivity"
          android:process="com.heyanle.new.process"/>
<activity android:name=".SActivity"
          android:process=":s"/>

android:process 属性是一个字符串,表示这个进程的代号。当不指定时,默认为包名,也就是默认进程。这里有两种模式:

  • : 开头表示该进程为该应用的私有进程,其他应用无法和它跑进同个进程,同时,这种情况下系统会默认在该进程中加入包名前缀,即实际上该进程为 com.heyanle.new:s
  • 不以 : 开头表示该字符串为进程的完整名称,而这种进程为全局进程,其他应用可通过 ShareUID 和他跑进同个进程,当然只有两个相同 ShareUID 并且 签名 相同的应用才能在同个进程中运行。

2.2.2 多进程机制

事实上,在两个进程中运行的程序完全可以看成是两个应用,他们只是将同样的代码放进两个应用而已,具体说来有以下特点:

  • 静态成员和单例模式失效
  • 线程同步机制失效
  • SharedPreferences 可靠性下降,虽然看成了两个应用,在内存中处在不同区域,但 SharedPreferences 本质上是通过读文件来进行数据持久化的,也就是说虽然他们在两个进程,但是包名还是一致,对应文件还是一致,只不过会有并发读写的问题。
  • Application 会多次创建

多进程的通信已经不是应用层面的问题,是操作系统层面的问题,操作系统会为开发者提供 IPC 的工具,当然,安卓也提供了工具,也就是接下来要介绍的。

2.3 IPC 基础

Android 编程中,抽象是一个重要的概念。将一些具体的功能抽象成一个名词,然后对应一个接口或一个类。而在 Android 中,有这样几个名词与 IPC 息息相关: Serializable、Parcelable 以及 Binder。其中前两个是序列化的抽象,而 Binder 是 Android 提供的一种 IPC 通信工具。这里分别做介绍。

2.3.1 Serializable 接口

Serializable 是 Java 提供的一个序列化接口。当然这里首先要介绍一下序列化的概念:

  • 序列化是将一个对象转化为一串二进制流或字符串,以便能通过文件保存或通过网络传输的过程。与其对应的过程称为反序列化,就是将序列化后的一串东西变回对象的过程。

这里举个很简单的例子,例如我们有以下类:

public class User{
    public String name;
    public String uid;
}

我们有这个类的一个实例,其中 name = "何言"uid = "333777"。那我们可以这样实例化这个对象,直接将键值对写进去,即 name=''何言'',uid='333777'。这样就完成了这样一个序列化的内容,对于这个字符串,我们可以保存在文件中,也可以通过网络传输给其他客户端。而接收方得到这样一串字符串后只需要解析这个字符串,并实例化一个新的对象并赋值即可。

  • 反序列化后的对象实际上并不是序列化的对象,只是我们将其内部成员变量的值设置为一样。对于业务逻辑来说,可以看成是同一个对象,但本质不是同一个。类似于调用了 clone 方法克隆的对象。

来看看 Serializable 是如何实现序列化的吧:

Serializable 的实现方法很简单,首先我们需要让实体类实现这个接口并声明一个 serialVersionUID 变量,如下图:

public class User implements Serializable{
   
    private static final long serialVersionUID = 12313131L
    
    public String name;
    public String uid;
}

关于 serialVersionUID 的作用之后再说明,先来看看如何进行序列化和反序列化。

如果要实现这个类的序列化和反序列化,我们需要用到一个 I/O流 —— ObjectOutputStreamObjectInputStream

例如下图,我们将一个 User 类序列化到文件中:

User user = new User();
user.name = "何言";
user.uid = "333777";
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("何言.obj"));
out.writeObject(user);

注意这里写入的是二进制流,如果直接当文本打开会出现乱码,因此这里采用 obj 后缀。实际上文件就是文件,什么后缀问题都不大。

然后我们可以通过反序列化将文件还原成对象:

ObjectInputStream in = new ObjectInputStream(new FileInputStream("何言.obj"));
User user = (User)in.readObject();

注意这里序列化和反序列化是深拷贝,也就是说对于对象持有对象的情况,也会对被持有的对象进行序列化,即使被持有的对象没有实现 Serializable,实际上,无论怎么持有,最终还是会回到几种基本数据类型。

然后来说说 serialVersionUID 的作用。其实就是一个标记,当序列化的时候,程序会将该 UID 写进序列化文件中,而反序列化的时候,系统会寻找你反序列化的类中指定的 UID 与 文件中的是否一致,如果不一致则抛出异常。

其实可以看成是版本控制,当你这个类因为业务的堆叠新增了许多变量的时候,程序猿可以手动修改类中这个 UID,而在反序列化时加个判断,如果出错则新建对象之类的。

2.3.2 Parcelable 接口

Parcelable 是 Android 提供的序列化接口,只要实现了这个序列化接口,则可以将对象序列化并通过 Intent 和 Binder 传递。以下是一个典型用法:

//Student.java
public class Student implements Parcelable{
    public String name;
    public String uid;
    public Clazz clazz;
    
    @Override
    public int describeContents(){
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel out, int flags){
        out.writeString(name);
        out.writeString(uid);
        out.writeParcelable(clazz, 0);
    }
    
    private Student(Parcel in){
        name = in.readString();
        uid = in.readString();
        clazz = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }
    
    public static final Parcelable.Creator<Student> CREATOR = 
        new Parcelable.Creator<Student>{
        public Student createFromParcel(Parcel in){
            return new Student(in);
        }
        public Student[] newArray(int size){
            return new Student[size];
        }
    }
}
//Clazz.java
public class Clazz implements Parcelable{
    public String name;
    
    // 其他内容省略,大同小异
    
}

可以看到,Parcelable 接口并不能直接帮你实现序列化,其内部是构造了一个 Parcel 方法来实现的,先来说说这个对象:

  • Parcel 对象是一个可以将数据打包成二进制流的对象,其有许多方法,与序列化有关的有 writeXX 与 readXX 方法,可以写入数据和读取数据。不过要注意的是其内部可以看成一个 byte[] 和一个指针,所以 write 与 read 的顺序要对应。(在实例化与反序列化的时候系统会帮我们将指针回到起点)

然后就可以看看这个接口都帮我们加入了啥。

  • 首先是一个私有构造方法:private Student(Parcel in) 就是通过一个 Parcel 对象还原这个方法,注意如果要读取另一个可序列化的对象的时候要传入上下文类加载器。
  • 然后是一个方法:public int describeContents() 这个方法返回这个对象是否含有文件描述符,1 表示含有,对应常量为 CONTENTS_FILE_SESCRIPTOR,文件描述符在大部分 UNIX 系统与大部分类 UNIX 系统中是操作文件的数据结构,其处理得不好很容易引发内存泄漏、安全问题等。因此如果你的实例化对象中包含文件操作符,则系统在使用中遇到某些危险的情况会抛出异常。绝大部分情况直接返回 0 即可。
  • 然后是:public void writeToParcel(Parcel out, int flags) 这个就是将该对象序列化为 Parcel 的接口,我们内部自己按顺序将变量内容写进 Parcel 中。第二个参数目前只有两个值 0 和 1,0 表示普通情况,1 对应的常量是 PARCELABLE_WRITE_RETURN_VALUE ,表示该对象要作为返回值返回,序列化后不能释放内部的内存。
  • 然后是一个静态常量,类型是 Parcelable.Creator<Student> ,这是创建的工具,有两个方法,一个是根据 Parcel 创建对象,还有一个创建指定长度的数组(因为 Java 使用泛型擦除,因此如果要新建数组,那么无法知道是要新建哪个类型的数组,同理创建对象也类似)

可以看到 Parcelable 是非常麻烦的 API,并且其不支持深拷贝,需要深拷贝的话需要另一个对象也实现 Parcelable 对象。不过它的效率会比 Serialzable 高。Android 中自带的许多类都默认实现了该接口,比如 Intent、Bundle、Bitmap 等,同时对于容器 List 或 Map 等,只要它里面每个元素都是可序列化的,那么整个容器也是可序列化的。

在选择上,如果只是在 Android 中使用内存序列化(例如 IPC),那么推荐使用 Parcelable,但如果是需要将对象存成文件或者通过网络发送,则推荐使用 Serialzable,因为 Serialzable 直接与 IO流挂钩,可以直接写和读。

2.3.3 Binder

Binder 是 Android 中的一个类,实现了 IBinder 接口。在 Android 应用层中,Binder 是客户端和服务端通信的媒介(Activity 与 Service)。当 bindService 的时候,服务端会返回一个 Binder 对象,通过这个 Binder 对象,客户端可以获取服务提供的服务。而这里的 Binder 可以是同个进程的普通服务,和基于 AIDL 的跨进程服务。

普通 Binder 就是一个简单的对象,无法触及底层跨进程的核心,因此这里采用 AIDL 来分析 Binder。

首先我们写一个 Book 的实体类:

// Book.java
package com.heyanle.aidl;
import xx
public class Book implements Parcelable{
    public int bookId;
    public String bookName;
    /*
     * Parcelable 具体代码省略 
     */
}

要让一个实体类跨进程,首先需要实现 Parcelable 接口,这里相关写法可以参照上文,不给出。

然后我们再新建两个文件: Book.aidlIBookManager.aidl

// Book.aidl
package com.heyanle.aidl;

parcelable Book;
// IBookManager.aidl
package com.heyanle.aidl;
import com.heyanle.aidl.Book;
interface IBookManager{
    List<Book> getBookList();
    void addBook(in Book book);
}

AIDL 的作用是为 Binder 提供序列化,其中这种序列化除了对成员变量的序列化,还包含对方法的序列化,当我们写完 AIDL 文件后重新 Build 项目,可以看到 Android Student 为我们生成了对应的 Java 类,具体代码可以自己生成查看,这里就不贴出来了。

可以看到系统为我们生成的 IBookManager.java 继承了 IInterface 这个接口,同时自己还是个接口,内部定义了我们写的两个方法。同时对每个方法都做了 id 标记。

AIDL 的内部实现其实比较复杂,这里简单讲解一下就是,在跨进程条件下,对于服务端,需要返回一个自己实现的 IBookManager 对象,而对于客户端则会获得一个 IBookManager 接口对象。当客户端调用 IBookManager 接口中方法时,系统会将方法 id,参数对象序列化后发送给服务端,而服务端收到后调用自己实现的对象的对应方法,然后再将返回值 序列化后返回给客户端。

其实 Aidl 文件完全可以不写的,我们可以直接按照其生成的 Java 文件的格式写即可。之所以要使用 aidl 是为了让系统自动生成。

这个接口实现的核心在于它内部类 Stub 和 Stub 的内部代理类 Proxy,下面详细介绍这两个类中方法的含义:

  • DESCRIPTOR :Binder 的唯一标识,一般为包名 + 类名
  • asInterface(android.os.IBinder obj) :将服务端的 Binder 对象转换成客户端所需的 ALDL 接口类型的对象。如果处于同一进程,则直接返回 Stub 对象本身给服务端,否则则是返回系统封装后的 Stub.proxy 代理类。
  • asBinder : 返回当前 Binder 对象
  • Boolean onTransact(int code, Parcel data, Parcel reply, int flags) :当客户端调用方法,即向服务端发送要调用的方法 id 和参数的时候,服务端 Binder 中的这个方法会被调用,这个方法是运行在 Binder 线程中。在方法中可以通过参数获取 方法 id 和 客户端调用的参数。调用对应方法后则向 reply 中写入返回值,reply 中的内容会被发送回 客户端。这个方法如果返回 false,那么客户端则访问失败,这里可以作鉴权。
  • Proxy#getBookList :跨进程时,客户端最终拿到的是 Proxy 对象,这个对象也实现了 IBookManager 接口,而客户端可以使用里面的方法。显然这个方法的作用就是将方法 id,参数 打包成 Parcel 还有 承接返回值的 Parcel 发送给服务端,发送的动作需要调用 transact 方法,这个方法会阻塞当前线程(当然不是主线程,是 Binder 线程),然后将数据发送给 服务端,然后等待服务端返回的数据,然后回到 getBookList 方法,然后将数据 return 回客户端。
  • Proxy#addBook:这个方法和 getBookList 类似。

以下是 Binder 工作机制图:

Binder 工作机制.png

至此,对于 Binder ,相信你已经有个大概的了解,至于如何使用,下一节会讲到。我们先来看看 Binder 两个很重要的方法 linkToDeath 和 unlinkToDeath 。当我们的 Binder 再运行的时候,如果服务端异常终止,这时候 Binder 连接回断裂(Binder 死亡),会导致我们调用失败,同时我们客户端并不知道连接断裂。为了解决这个问题,我们可以给 Binder 设置死亡代理。当 Binder 死亡的时候我们可以收到一个通知。

首先我们需要实现一个死亡接收器,可以以内部匿名类的形式:

private IBinder.DeathRecipient recipient = new IBinder.DeathRecipient(){
    @Override
    public void binderDied(){
        // TODO: 死亡时候会执行该方法
    }}

然后在我们客户端拿到 Binder 之后绑定这个死亡接收器:

binder.linkToDeath(recipient, 0)

当然,有 link 就有 unlink ,一般在我们 Binder 死亡监听器里需要把自己 unlink ,因为通常当 Binder 死亡的时候我们需要重新连接,重新获取一个 Binder 。同时,当我们需要知道当前 Binder 是否死亡的时候,可以直接调用 Binder.isBinderAlive 判断。

2.4 Android 中 IPC

接下来是正题啦,再了解序列化和 Binder 之后,我们来看看 Android 中可以用的 IPC 方式:

2.4.1 使用 Bundle

首先,额外开进程必须依赖于四大组件,ContentRrovider 天然支持 IPC,之后会讲。这里先看另外三个,我们知道 Activity、Service 和 Receiver 时可以使用 Intent 发送数据,而 Intent 里可以放 Bundle,并且 Intent 和 Bundle 方法都实现了 Parcelable 方法,因此我们可以讲数据放到 Bundle 中,然后将 Bundle 放进 Intent 中来通讯。不过 Bundle 并不支持所有类型,它支持的数据类型可以去看其 put 系列方法。当 Intent 启动的组件是另外一个进程的时候,系统会帮我们完成序列化、传送、反序列化的工作。这是最简单的一种 IPC 方式。

2.4.2 使用文件

这个是一种不错的进程间通信方式,通过文件系统来通信。例如:我们可以在进程 A 中将 User 类序列化到文件中,然后在进程 B 中将文件反序列化。当然,要尽量避免多进程同时对同一个文件的写操作,在 Linux 系统中没有对并发写文件进行限制,而 Android 基于 Linux,因此如果同时写则可能会出问题。

2.4.3 使用 Messenger

Messenger 翻译为信使,其底层时间为 AIDL,是安卓封装的一种轻量级 IPC 方案。其实现的 IPC 是 C/S 模型,则客户端发请求,服务端回响应。其中服务端一般为 Service :

  1. 服务端

在 Service 中,我们必须重写 onBind 方法,并返回一个 Binder,此时我们直接将 Messenger 对象的 getBinder 方法拿到的对象返回即可,当然要先新建一个 Messenger 并实现一个消息处理器 —— Handler

public class MyService extends Service{
 private Handler mHandler = new Handler(Looper.getMainLopper()){
     @Override
     public void handleMessage(Message msg){
         // TODO:处理数据
     }
 }
 private Messenger mMessenger = new Messenger(mHandler);
 @Override
 public IBinder onBind(Intent intent){
     return mMeesenger.getBinder();
 }}

服务端就搞定了,当客户端发送数据的时候,我们可以在 Handler 中处理。注意这里 Message 是系统封装好的一个支持序列化的类,其内部成员变量有 what、arg1、arg2、Bundle 以及 replyTo 。其中 what 一般作为服务 Id,客户端想要什么服务。此外,Message 还有一个 Object 类型的变量 obj,可惜 在 Android 2.2 之前在 IPC 条件下无法工作,哪怕在 Android 2.2 之后,也支支持系统自带的实现 Parcelable 对象,不过我们可以用 Bundle。

这里要特别注意 replyTo,这是一个 Messenger 对象,当客户端请求的服务需要结果时,客户端需要将用于接收数据的 Messenger 放进 replyTo 。在服务端中就通过该对象返回数据,这也是为什么 Messenger 是 C/S 模型的原因,服务端并不能主动拿到客户端的连接(当然你把 replyTo 存起来也是可以的,不过不建议)。

  1. 客户端

客户端如果需要接收服务端的相应,则需要两个 Messenger,先上代码:

class MyActivity extends Activity{
 private mHandler = new Handler(Looper.getMainLooper()){
     @Override
     public void handleMessage(Message msg){
         // TODO:处理数据
     }
 }
 private mResponseMessenger = new Messenger(mHandler);
 private mRequestMessenger ;
 private ServiceConnection mConn = new ServiceConnection(){
     public void onServiceConnected(ComponentName className, IBinder service){                                              mRequestMessenger = new Messenger(service);
     }
     public void onServiceDisconnected(ComponentName className){
     }
 }}

然后我们通过 mConn 这个 ServiceConnection 来 bindService,然后通过 mRequestMessenger 发送的 Messenger 对象就能发送到服务端了,而对于需要可回复,我们需要将其 replyTo 赋值为 mResponseMessenger 即可。

2.4.4 使用 AIDL

Messenger 是一个非常方便的 IPC 方法,但是其有缺陷。首先,其消息处理使用的是 Handler,而 Handler 需要传入 Looper 对象,因此它处理数据是串行的,当多个数据来的时候,只能一个一个处理。其次,Messenger 本质上是通过发送和接收 Message 对象来通信,你需要将操作封装成 Message,而不能通过直接调用取返回值的方式。因此,如果你有上述需求,则需要直接使用 AIDL 来通信了。当然 Messenger 底层还是使用的 AIDL ,而这里我们自己通过 AIDL 来实现。

首先,我们需要在客户端和服务端都写 aidl 文件与涉及到的实体(需实现 Parcelable),如果两边属于同个项目,则直接用一份即可,这里以上面写的 Book.java、 Book.aidl 和 BookManager.aidl 为例:

// Book.java
package com.heyanle.aidl;
import xx
public class Book implements Parcelable{
    public int bookId;
    public String bookName;
    /*
     * Parcelable 具体代码省略 
     */
}
// Book.aidl
package com.heyanle.aidl;

parcelable Book;
// IBookManager.aidl
package com.heyanle.aidl;

import com.heyanle.aidl.Book;

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

然后来看看具体实现把:

  1. 服务端

    public class BookManagerService extends Service{
        private Binder mBinder = new IBookManager.Stub(){
            @Override
            public List<Book> getBookList() throws RemoteException{
                // TODO :具体实现
            }
            
            @Override
            public void addBook(Book book) throws RemoteException{
                // TODO :具体实现
            }
        }
        
        @Override
        public IBinder onBind(Intent intent){
            return mBinder;
        }
    }

搞掂,其中 IBookManager 类是 Android Studio 根据我们的 aidl 文件生成的类。

  1. 客户端

    public class BookActivity extends Activity{
        private IBookManager mService;
        private ServiceConnection mConn = new ServiceConnection(){
            public void onServiceConnected(ComponentName className, IBinder service){
                mService = IBookManager.Stub.asInterface(service);
            }
            public void onServiceDisconnected(ComponentName className){
                
            }
        }
    }

完成,当我们使用 mConn 来 BindService 的时候,我们就得到了一个 IBookManager 对象,我们直接调用这个对象的对应方法,则在服务端也会调用对应方法,同时返回值也会返回。

不过有一点需要注意,当你客户端使用 IBookManager 调用方法的时候,其内部本质上是将 方法 ID 和参数的序列化发送到服务端然后反序列化,服务端执行后在将结果序列化返回回来,因此该方法回阻塞当前线程,因为要等 IPC 操作,因此直接调用有可能会导致 ANR,需要注意。

当然 Binder 远不止这些,其中还有许多内容,比如跨进程监听器,连接鉴权等,这部分在书上也有说明,这里就点到为止了。

2.4.5 使用 ContentProvider

可以直接看 官方文档,这里不做详述

2.4.6 使用 Socket

直接使用 Socket 通过网卡来通信比较简单,这里也不做详述。

2.5 Binder 连接池

在我们使用 aidl 的时候,我们发现每写一个 aidl 就需要一个 service,这其实是没有必要的,因此我们可以只使用一个 service 来管理多个 aidl,也就是多个 binder。而这个 service 就可以理解为一个 Binder 连接池。

这里面主要的难点在线程同步上,直接来看看代码把:

首先先写两个 aidl 作为服务:

// SecurityCenter.aidl
interface ISecurityCenter {
    String encrypt(String content);
    String decrypt(String password);
}

// Compute.aidl
interface ICompute {
    int add(int a, int b);
}

随便写的两个服务,实现类也随便写写:

// SecurityCenterImpl.kt
class SecurityCenterImpl : ISecurityCenter.Stub() {

    companion object{
        const val SECRET_CODE = 'K'
    }

    override fun encrypt(content: String): String {
        val sb =  StringBuilder()
        for(c in content){
            sb.append((c.toInt() xor SECRET_CODE.toInt()).toChar())
        }
        return sb.toString()
    }

    override fun decrypt(password: String): String {
        return encrypt(password)
    }
}

// ComputeImpl.kt
class ComputeImpl : ICompute.Stub(){

    override fun add(a: Int, b: Int): Int {
        return a+b
    }
}

可以看到非常简单,然后我们来写一个用作 BinderPool 的 aidl

// BinderPool.aidl
interface IBinderPool {
    IBinder queryBinder(int binderCode);
}

这个方法可以根据传入的 int 来返回特定的 IBinder

实现类:

// BinderPoolImpl.kt
class BinderPoolImpl : IBinderPool.Stub() {

    companion object{
        const val BINDER_CODE_SECURITY_CENTER = 0
        const val BINDER_CODE_COMPUTE = 1
    }

    override fun queryBinder(binderCode: Int): IBinder? {
        return when(binderCode){
            BINDER_CODE_COMPUTE -> ComputeImpl()
            BINDER_CODE_SECURITY_CENTER -> SecurityCenterImpl()
            else -> null
        }
    }
}

然后是服务端,也非常简单:

// BinderService.kt
class BinderService : Service() {

    private val binderPool = BinderPoolImpl()
    override fun onBind(intent: Intent?): IBinder {
        return binderPool
    }
}

接下来是重点了,我们来写一个 BinderPool 的工具类:

class BinderPool(
    context: Context){

    companion object{
        @SuppressLint("StaticFieldLeak")
        @Volatile  var INSTANCE: BinderPool? = null

        fun with(context: Context): BinderPool{
            if(INSTANCE == null){
                synchronized(BinderPool::class.java){
                    if (INSTANCE == null){
                        INSTANCE = BinderPool(context)
                    }
                }
            }
            return INSTANCE!!
        }
    }

    private lateinit var connectBinderPoolCountDownLatch: CountDownLatch
    private lateinit var binderPool: IBinderPool
    private val context: Context = context.applicationContext
    private val conn = object: ServiceConnection{
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            binderPool = IBinderPool.Stub.asInterface(service)
            runCatching {
                binderPool.asBinder().linkToDeath(deathRecipient, 0)
            }.onFailure {
                it.printStackTrace()
            }
            connectBinderPoolCountDownLatch.countDown()

        }

        override fun onServiceDisconnected(name: ComponentName?) {
            // nothing
        }
    }

    private val deathRecipient = object: IBinder.DeathRecipient{
        override fun binderDied() {
            binderPool.asBinder().unlinkToDeath(this, 0)
            connectBinderPoolService()
        }
    }

    @Synchronized
    private fun connectBinderPoolService(){
        connectBinderPoolCountDownLatch = CountDownLatch(1)
        context.bindService(Intent(context, BinderService::class.java), conn,
            Context.BIND_AUTO_CREATE)
        runCatching {
            connectBinderPoolCountDownLatch.await()
        }.onFailure {
            it.printStackTrace()
        }
    }

    fun queryBinder(binderCode: Int): IBinder? {
        runCatching {
            return binderPool.queryBinder(binderCode)
        }.onFailure {
            it.printStackTrace()
        }
        return null
    }
}

搞定,使用的时候我们可以:

val iBinder = BinderPool.with(this)
            .queryBinder(BinderPool.BINDER_CODE_SECURITY_CENTER)
val securityCenter = ISecurityCenter.Stub.asInterface(iBinder) as ISecurityCenter

至此,BinderPool 完成,不过有几点需要注意:

  1. Service 将会在第一次调用 BinderPool.with 的时候启动,并且会与 ApplicationContext 绑定
  2. BinderPool.with 使用了 Latch 来等待 binder service 成功,因此如果调用时还未 bind 或者正在 bind 的时候,该方法会阻塞当前线程,建议放到子线程工作