IPC:Inter-Process Communication,是指2个进程之间进行数据交换的过程,一般情况下,安卓的多进程是指一个应用中有多个进程,有时候为了得到多份内存空间,或者一些其他的特殊要求,ContentProvider其实也是一种IPC。
开启多进程模式很简单,只有一种方法,就是在manifest文件里指定4大组件的process属性,为process属性指定一个进程名称。系统默认的进程名称为包名,我们有2种方式命名,一种是“:名称”,这种名称会把包名自动地加到进程名称前面,这种进程属于私有进程,其他应用组件不能与它跑在同一进程里;另一种是“名称”,这种进程属于全局进程,其他应用可以通过ShareID与它跑在同一进程。
开启多进程后,可以在终端通过输入adb shell ps 来查看虚拟机的进程,也可以加管道 | 包名 来只显示应用的进程。
安卓开启每一个进程,都会为它分配一个独立的虚拟机和Application,以及内存空间,同一个应用内的类,实例在不同的进程中会产生不干扰的副本。多进程带来的问题主要有:静态成员和单例模式完全失效;线程同步机制完全失效;SharedPreferences可靠性下降,因为不支持2个进程同时去执行写操作;Application会多次创建。
为了让对象可以保存在磁盘中,或者可以通过网络进行传输,采用序列化的方式来处理对象。Serializable方式,只要对象的类继承自这个接口就可以实现对象序列化了,其中有个serialVersionID,如果不指定这个值,则系统会把当前类的serialVersionID赋予被序列化的对象,在反序列化时,如果此时的类的当前serialVersionID等于当时序列化时的类的serialVersionID,则反序列化会成功,否则失败;当自己指定了一个serialVersionID值时,即使当前类的serialVersionID与之前序列化时的类ID不同,也可以反序列化成功,系统会尽可能地帮我们恢复数据。需要注意的是,类的静态成员不参与序列化,同时用transient指定的成员也不参与序列化,transient的意思的短暂的,即告诉系统,这个实例变量是短暂的,不需要存储;另外,当需要序列化的类里面包含别的类,则这个内部类也要实现序列化才行。序列化的经典写法:
ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( "xxx.txt" ) );
out.wirteObject(序列化类的实例); out.close();
ObjectInputStream in = new ObjectInputStream( new FileInputStream( "xxx.txt: ) );
序列化实例 = (序列化类)in.readObject(); in.close();
另外一种安卓自己的序列化方式为Parcelable,需要序列化的类继承自该接口后,需要手动地编写哪些成员被写入包里,在反序列化时也要手动创建对象创造器,然后指定获得包后如何来恢复对象,另外需要再写一个describeContents的方法,它在大多数的情况下只要返回0就行了,除非返回的对象里有文件描述符。Parcelable的经典写法是:
需要注意的是,序列化类的私有属性在过程中是不受保护的;反序列化时,读取类型属性需要使用当前线程来获得上下文类加载器,否则会失败;本来writeToParcel和createFromParcel是一对对应的函数,但是序列化时需要返回序列化对象数组,所以把2个方法封装在Parcelable.Creator
有些类实现了Parcelable接口,比如Intent,Bitmap,Bundle等,List和Map也可以序列化,只要元素可序列化。
Parcelable和Serializable,如果是内存序列化,也就是进程间共享内存实例时,推荐使用前者,效率比较高;如果时储存磁盘或者网络传输,推荐使用后者,实现方式比较简单。
Binder的工作机制:
Binder内部有个代理类proxy,它要在客户端运行,还有个onTransact的方法,运行在服务端的Binder池里。
服务端通过onBind方法返回自己封装好的Binder对象给客户端的ServiceConnection里,客户端可以使用该Binder对象的proxy类的方法进行数据交流,具体的过程是:proxy类的方法需要输入类型的Parcel对象,输出型的Parcel对象,以及返回值对象;在获得输入型对象后,会调用服务端Binder池里的onTransact方法,把输入型对象传递给onTransact,并把当前线程挂起,然后就是服务端的运行了;在服务端,onTransact方法需要接收4个参数,请求码Code——确定客户端要请求的方法,输入型Parcel——来自客户端的输入型Parcel,输出型Parcel——把要返回给客户端输出型Parcel的内容封装进去,标签值;如果onTransact返回的是false,则客户端的请求将会失败,可以通过返回值来进行权限验证;服务端执行完onTransact方法后(这些方法就是服务端自己绑定给Binder的方法),就会激活客户端线程,回到客户端的proxy类的方法里,并把onTransact的输出型Parcel传递给proxy类的方法的第二个参数,然后proxy类方法就把该值取出,并使用该值,这样就完成了客户端请求服务端服务的请求。
整体看起来,客户端的proxy类方法使用第一个参数来激活服务端的onTransact,而onTransact使用第三个参数激活客户端和proxy类的方法。
Binder是在服务端创建的,创建时需要给Binder绑定服务内容,之后Binder运行在服务端的Binder线程池里;在客户端请求绑定服务后,通过3个事物建立连接:bindService方法、onBind方法、ServiceConnection类,其中,服务端的onBind返回服务端的Binder对象到客户端的ServiceConnection类的方法里触发回调接口,客户端获得Binder实例后就可以在建立了绑定的基础上进行数据传输了。也就是说,主要是分2个事件,一个是建立绑定,一个是进行服务调用或者说是数据交流,而Binder就是进行服务调用或数据交流的媒介,它随着绑定过程到达客户端,使得双方拥有同一个Binder实例,并根据Binder里proxy和onTransact的运行特性进行数据交流。
有一个问题需要注意,当服务端的进程被终止时,Binder就会死亡,那么客户端的请求就会失败。解决的办法是为Binder设置死亡代理DeathRecipient类,当Binder死亡时,如果客户端请求服务,则会反馈回给用户,通知客户端Binder死亡,然后让客户端采取重新连接或者其他的操作,从分析就知道,DeathRecipient必须是一个需要客户端传进回调接口的接口,所以创建方式就是在客户端先创建一个DeathRecipient类对象,然后实现该类里规定的回调接口binderDied(),然后给onBind返回的Binder对象设置死亡代理,binder.linkToDeath(DeathRecipient实例,0)。
使用Bundle也可以进行多进程间的通信,不过类型必须是Bundle支持的。具体的实现:创建一个Bundle对象,然后给Bundle实例put类型并写入对应键值,然后设置intent对象的Extras,把Bundle实例附入;读取的时候,使用intent来getExtras取出Bundle,然后使用Bundle的get类型+键值读出数据。如果不是位于同一个应用里的多进程,要为intent制定包名。
共享文件也可以进行多进程通信。