2.1 Android IPC 简介

  1. 线程是 CPU 调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在 PC 和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程
  2. Windows 上可以通过剪贴板、管道和油槽等来进行进程间通信;Linux 上可以通过命名管道、共享内存、信号量等来进行进程间通讯;Android 上可以通过 Binder、Socket 实现进程间通讯

2.2 Android 中的多进程模式

2.2.1 开启多进程模式

在 Android 中使用多进程的方法,那就是给四大组件(Activity、Service、Receiver、ContentProvider)在 AndroidMenifest 中指定 android:process 属性。其实还有另一种非常规的多进程方法,那就是通过 JNI 在 native 层去 fork 一个新的进程。

实例:

// 包名为 com.example.application
<service
android:name=".service.MyRemoteService"
android:process=":remote" />
<service
android:name=".service.MyRemoteServiceII"
android:process="com.example.application.remote" />

当 MyRemoteService 启动时,系统会为它创建一个单独的进程 ”com.example.application:remote“;

当 MyRemoteServiceII 启动时,系统会为它创建一个单独的进程 ”com.example.application.remote“;

没有指定 process 属性的,那么它运行在默认进程中,默认进程就是包名。

”:“ 的含义是指要在当前的进程名前面附加上当前的包名,这是一种简写。其次,进程名以 ”:“ 开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以 ”:“ 开头的进程属于全局进程,其他应用通过 shareUID 方式可以和它跑在同一个进程中。

2.2.2 多进程模式的运行机制

一般来说,使用多进程会造成如下几方面的问题:

  1. 静态成员和单例模式完全失效
  2. 线程同步机制完全失效
  3. SharedPreferences 的可靠性下降
  4. Application 会多次创建

第一、二个问题是类似的,既然都不在同一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是用一个对象。第三个问题因为 SharedPreferences 底层是通过读/写 XML 文件来实现,并发读/写都有可能出现问题。第四个问题,运行在不同进程中的组件是属于两个不同的虚拟机和 Application 的。

2.3 IPC 基础概念介绍

2.3.1 Serializable 接口

Serializable 是 Java 所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用 Serializable 只需要在类的声明中指定 serialVersionUID 。

private static final long serialVersionUID = 233333333333333L

这个 serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同才能够正常地被反序列化。

默认实现 Serializable 不会自动创建 serialVersionUID 属性,我们可以开启警告,这样按下代码补全就自动生成 serialVersionUID 。

Serializable_setting.png
Serializable_setting.png

以下两点需要特别注意:

  1. 静态成员变量不属于类对象,所以不会参与序列化过程
  2. 用 transient 关键字标记的成员变量不会参与序列化过程

2.3.2 Parcelable 接口

Parcelable 也是一个接口,只要实现了这个接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。Android Studio 支持一键自动生成 Parcelable 代码,所以不用怕麻烦。

Parcelable.png
Parcelable.png

Serializable 和 Parcelable 如何选取?

Serializable 是 Java 中的序列化接口,其使用起来非常简单但是开销很大,整个过程需要大量 I/O 操作。一般在保存数据到 SD 卡或者网络传输时建议使用 Serializable 即可,虽然效率差一些,好在使用方便。

而 Parcelable 是 Android 中的序列化方式,更适合在 Android 平台上,缺点是使用起来稍微麻烦,但是它的效率很高。而在运行时数据传递时建议使用 Parcelable,比如 Intent,Bundle 等。

2.3.3 Binder

直观来说,Binder 是 Android 中的一个类,它实现了 IBinder 接口。从 IPC 角度来说,Binder 是 Android 中的一种跨进程通讯方式;从 Android Framework 角度来说,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager,等等)和相应 ManagerService 的桥梁;从 Android 应用层来说,Binder 是客户端和服务端进行通讯的媒介。

下面讲几个重要的方法:

  • asInterface

    // 用于将服务端的 Binder 对象转换成客户端所需的 AIDL 接口对象
    public static com.meiji.IMyAidlInterface asInterface(android.os.IBinder obj) {
    // 客户端和服务端位于同一进程,返回服务端的 Stub 对象本身
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.meiji.IMyAidlInterface))) {
    return ((com.meiji.IMyAidlInterface) iin);
    }
    // 返回封装后的 Stub.Proxy 对象
    return new com.meiji.IMyAidlInterface.Stub.Proxy(obj);
    }
  • onTransact

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    // 服务端通过 code 确定客户端请求的目标方法
    switch (code) {
    case TRANSACTION_basicTypes: {
    data.enforceInterface(DESCRIPTOR);
    int _arg0;
    // 从 data 中取出参数
    _arg0 = data.readInt();
    // 然后执行目标方法
    this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
    // 执行完毕后,向 reply 中写入返回值
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
    }
    }
    return super.onTransact(code, data, reply, flags);
    }
  • Proxy#toUpperCase

    // 这个方法是我们在 AIDL 文件里定义的接口实现方法,当客户端调用此方法
    @Override
    public String toUpperCase(java.lang.String aString) throws android.os.RemoteException {
    // 创建输入型 Parcel 对象 _data
    android.os.Parcel _data = android.os.Parcel.obtain();
    // 创建输出型 Parcel 对象 _reply
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.lang.String _result;
    try {
    _data.writeInterfaceToken(DESCRIPTOR);
    // 把参数信息写入 _data 中
    _data.writeString(aString);
    // 调用 transact 发起 RPC(远程调用过程),同时挂起当前线程,然后服务端 onTransact 被调用
    mRemote.transact(Stub.TRANSACTION_toUpperCase, _data, _reply, 0);
    _reply.readException();
    // 写入服务端 onTransact 返回的数据
    _result = _reply.readString();
    } finally {
    _reply.recycle();
    _data.recycle();
    }
    // 最后返回 _reply 中的数据
    return _result;
    }

注意几点:

  1. 当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法时很耗时的,那么不能再 UI 线程中发起远程请求
  2. 由于服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了
  3. 通过 Binder 的 linkToDeath 方法可以给 Binder 设置一个死亡代理,当 Binder 死亡时 ,我们就会收到通知,这个时候可以重新发起连接请求从而恢复连接

Binder 的工作机制:

Binder工作机制.png
Binder工作机制.png

2.4 Android 中的 IPC 方式

2.4.1 使用 Bundle

四大组件中的三大组件(Activity、Service、Receiver)都支持在 Intent 中传递 Bundle 数据

2.4.2 使用文件共享

两个进程通过读/写同一个文件来交换数据,Android 系统允许并发读/写文件。使用 ObjectOutputStream 、ObjectInputStream 序列化一个对象到文件系统中同时从另一个进程中恢复这个对象。

SharedPreferences 也属于文件共享的一类,当面对高并发的读/写访问,SharedPreferences 有很大几率会丢失数据。

综上所示,文件共享方式适合在对数据同步要求不高的进程之间进行通讯,并且要妥协处理并发读/写的问题。

2.4.3 使用 Messenger

Messenger 可以翻译为信使,通过它可以在不同进程中传递 Message 对象,在 Message 中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递。Messenger 是一种轻量级的 IPC 方案,它的底层实现是 AIDL 。

Messenger工作机制.png
Messenger工作机制.png

2.4.4 使用 AIDL

Messenger 的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用 Mess anger 就无法做大了,但是可以使用 AIDL 来实现跨进程的方法调用。

AIDL 大致流程:首先创建一个 Service 和一个 AIDL 接口,接着创建一个类继承 AIDL 接口中的 Stub 类并实现 Stub 中的抽象方法,在 Service 的 onBind 方法中返回这个类的对象,然后客户端就可以绑定服务端 Service,建立连接后就可以访问远程服务端的方法了。

另外,由于服务端的方法(Stub 中的抽象方法)本身运行在服务端的 Binder 线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要再服务端方法中开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做。

2.4.5 使用 ContentProvider

和 Messenger 一样,ContentProvider 的底层实现同样也是 Binder,它是 Android 中提供的专门用于不同应用间进行数据共享的方式。系统预置了许多 ContentProvider,比如通讯录信息、日程表信息等。

ContentProvider 中有六个抽象方法:onCreate、query、update、insert、delete、getType,除了 onCreate 由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在 Binder 线程池中。

2.5 Binder 连接池

随着项目越来越大,AIDL 数量的增加,我们不能无限制地增加 Service,应该将所有的 AIDL 放在同一个 Service 中去管理。在这种模式下,服务端提供一个 queryBinder 接口,这个接口能够根据不同业务返回相应的 Binder 对象给客户端,客户端就可以进行远程方法调用了。

Binder连接池的工作原理.png
Binder连接池的工作原理.png

2.6 选用合适的 IPC 方式

IPC优缺点和适用场景.png
IPC优缺点和适用场景.png