2.1 Android IPC 简介
- 线程是 CPU 调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在 PC 和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程
- 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 |
当 MyRemoteService 启动时,系统会为它创建一个单独的进程 ”com.example.application:remote“;
当 MyRemoteServiceII 启动时,系统会为它创建一个单独的进程 ”com.example.application.remote“;
没有指定 process 属性的,那么它运行在默认进程中,默认进程就是包名。
”:“ 的含义是指要在当前的进程名前面附加上当前的包名,这是一种简写。其次,进程名以 ”:“ 开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以 ”:“ 开头的进程属于全局进程,其他应用通过 shareUID 方式可以和它跑在同一个进程中。
2.2.2 多进程模式的运行机制
一般来说,使用多进程会造成如下几方面的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences 的可靠性下降
- 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 。
以下两点需要特别注意:
- 静态成员变量不属于类对象,所以不会参与序列化过程
- 用 transient 关键字标记的成员变量不会参与序列化过程
2.3.2 Parcelable 接口
Parcelable 也是一个接口,只要实现了这个接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。Android Studio 支持一键自动生成 Parcelable 代码,所以不用怕麻烦。
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
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 文件里定义的接口实现方法,当客户端调用此方法
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;
}
注意几点:
- 当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法时很耗时的,那么不能再 UI 线程中发起远程请求
- 由于服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了
- 通过 Binder 的 linkToDeath 方法可以给 Binder 设置一个死亡代理,当 Binder 死亡时 ,我们就会收到通知,这个时候可以重新发起连接请求从而恢复连接
Binder 的工作机制:
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 。
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 对象给客户端,客户端就可以进行远程方法调用了。