序列化
在说跨进程通信之前,得先说一下序列化。
序列化又称为对象的持久化,因为我们知道,对象都是存在于内存中的。但是当我们说跨进程通信的时候,谈论的是两个不同的应用,不同的进程,有着不同的内存空间。所以有时候我们需要将某个对象持久化到存储设备上,然后在另一个进程中取出该对象进行使用,从而达到跨进程通信的目的。
现有的序列化方法一般是实现 Serializable 和 Parcelable 接口。
Serializable 是 Java 提供的接口,空接口,没有方法。实体类只需要实现该接口,就可以完成序列化。但是要注意,最好加上一个 serialVersionUID,否则反序列化时校验 serialVersionUID,如果不一致就会失败;
Parcelable 接口是 Android 提供的,比 Serializable 更高效,但实现起来更复杂一些。
一般来讲,内存层面的序列化都建议使用 Parcelable,如果是将对象序列化到存储设备或通过网络传输时,因为 Parcelable 实现起来太复杂,这两种情况下就可以考虑使用
Binder
说完序列化,接下来我们再说 Binder,Binder 是 Android 平台下的多进程通信的机制。
尽管 Android 基于 Linux 内核,但是并没有采用 Linux 自带的进程间通信的方式,而是重新设计了自己的 Binder 机制。
从进程间通信的角度来看,Binder 可以理解成一个虚拟的物理设备;
从 Android Framework 的角度看,Binder 是应用层 android.os.ServiceManager 连接 framework 各种 Manager 、ManagerService 的桥梁;
从 Android 应用层的角度来看,Binder 又是客户端和服务端的通信媒介,bindService 的时候,服务端给客户端返回一个 Binder 代理,通过该对象,客户端就可以与服务端进行数据通信和交互。
在 Android 开发中,一般在 Serivice 中才会应用到 Binder。
普通的 Service 或者说同一应用内,甚至同一应用不同进程之间的 Service 中的 Binder 机制都涉及不到进程间通信。
只有在真正意义上的,不同应用之间通信,才能涉及到 Binder 机制。
IPC 的几种方式
Bundle
一般就是四大组件之间进行通信,bundle 实现了 parcelable 接口,天然支持跨进程传输,是一种比较简单的跨进程通信方式。
文件共享
比如 sp 或者 xml 之类。一个进程写,一个进程读,从而实现跨进程通信。但是如果有高并发的问题,比较难处理。
ContentProvider
系统级别提供的,专门用于不同应用间共享数据的方式。通过数据库实现;
Socket
使用 Socket 套接字通过网络来传输字节流,从而实现进程间通信。但实现细节繁琐,也不实用;
Messenger
使用轻量级 IPC 方案 Messenger,Messenger 底层是封装的 AIDL。顾名思义,可以使用它跨进程传递 Message 对象,所以很明显,一般也需要配合 Handler 使用。如果要实现双向通信,重点在于客户端传递到服务端的 Message 对象的 replyTo 属性;
Messenger 的作用主要还是为了传递消息,如果需要调用服务端的方法或者有大量的并发请求,那就还是得手动实现 AIDL。
AIDL
手动实现 AIDL,这种稍微复杂一些。
AIDL 的重点在于实现 AIDL 文件,AIDL 的方法也是声明为接口形式,但是它又不是接口。
在 AIDL 文件中,只能声明方法,不能声明静态常量,而且也不是所有的数据类型都能得到支持,只能支持基本数据类型,String,char,List,Map,parcelable 对象。
如果方法参数不是基本数据类型,还要显示声明 in、out、inout 来标记输入还是输出;
如果方法参数是 parcelable 对象,那还得单独为该对象声明一个同名的 AIDL 文件,声明它是 parcelable;
服务端、客户端要实现完全相同的 AIDL 文件,包名都得完全相同;
AIDL 通信过程
下面单独说一下使用 AIDL 进行进程间通信的整个过程:
首先在 C/S 两端声明相同的 aidl 文件,然后让系统自动生成对应的 java 代码,这个类中会生成 Binder 实现 Stub (服务端使用,用来初始化 Binder 对象)和代理类(客户端使用,用来连接服务端)。
然后在服务端实现相应的 service,并实例化 binder 对象,通过 onBind 方法返回给客户端;在客户端对远端服务进行绑定,跨进程通信一般需要使用隐式 Intent 调用,然后在 onServiceConnected 回调中,通过 AIDL java 类是 asInterface 方法获取 binder 对象实例,然后客户端与服务端就建立了连接。
如果客户端对服务端发起调用,会通过 Proxy 代理类的方法,调用到服务端 binder 的 transact 方法,入参了 Parcel 对象 reply,然后客户端线程挂起,transact 内部会回调到 onTransact 方法中,然后就会执行服务端的对应实现方法,然后会在 onTransact 方法中对入参的 reply 对象进行写入,完成之后 onTransact 方法返回 true,服务端就会唤醒客户端,继续执行,客户端再将返回结果返回给调用者,完成整个流程的调用。
AIDL 注意点
需要注意的是,服务端的方法是运行在 Binder 线程池中的,一般需要处理高并发的情况,比如用 CopyOnWriteArrayList 代替 ArrayList,用 ConcurrentHashMap 代替 HashMap,用 AtomaticBoolean 代替普通的 Boolean。在这种情况下,服务端方法本身就可以执行大量耗时操作,所以不需要在服务端方法中再开线程去进行异步任务。
也是因为这一点,如果在 UI 线程访问服务端方法时,由于方法有可能是耗时任务,所以最好不要同时更新 UI,否则容易造成 ANR。如果在客户端有服务端方法的接口回调,也注意不可以直接更新 UI,因为本质上回调方法是执行在 Binder 线程的,非 Looper 线程更新 UI 也会抛出异常。
除此之外,Binder 是有可能意外挂掉的。所以最好事先 binder 的死亡代理,当 binder 死亡,我们可以选择重新建立远程服务连接。
同时,还应该给 AIDL 连接加上权限限制。具体可以在清单文件中声明需要的权限,并在 onBind 或者 onTransact 方法中进行权限验证,从而保证连接安全。