IPC机制
1、Android IPC简介
IPC是inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,指两个进程之间进行数据交换的过程。
最简单的情况下,一个进程中可以只有一个线程,即主线程,在Android里面主线程也叫UI线程,在UI线程里才能操作界面元素。很多时候,一个进程中需要执行大量耗时的任务,如果这些任务放在主线程中去执行就会造成界面无法响应,严重影响用户体验,这种情况在PC系统和移动系统中都存在,在Android中有一个特殊的名字叫做ANR(Application Not Responding),即应用无响应。解决这一问题就需要用到线程,把一些耗时的任务放在子线程中即可。
IPC不是Android中所独有的,任何一个操作系统都需要有相应的IPC机制,windows上可以通过剪贴板版、管道和油槽等来进行进程间通信;Linux上可以通过命令管道、共享内存、信号量等来进行进程间通信;对于Android来说,它是一种基于Linux内核的移动操作系统,它的进程间通信方式并不能完全继承自Linux,相反,它有自己的进程间通信方式。在Android中最有特色的进程间通信方式就是Binder了,通过Binder可以轻松地实现进程间通信。除了Binder,Android还支持Socket,通过Socket也也可实现任意两个终端之间的通信。
多进程的情况分为两种:
1)、一个应用因为某些原因自身需要采用多进程模式来实现;
2)、当前应用需要向其他应用获取数据,由于是两个应用,所以必须采用跨进程的方式获取所需的数据。
2、Android中多进程模式
通过给四大组件指定android:process属性,可以轻易地开启多进程模式,这看起来很简单,但是实际使用过程中暗藏杀机,多进程远远没有我们想的那么简单,有时候我们通过多进程得到的好处甚至都不足以弥补使用多进程说带来的代码层面的负面影响。
1)、开启多进程方式
正常情况下,在Android中多进程是指一个应用程序中存在多个进程的情况
1、在Android中使用多进程只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process属性,除此之外没有其他方法,也就是说我们无法给一个线程或者一个实体类指定其运行时所在的进程。
2、还有一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程,但是这个方法属于特殊情况,也不是常用的创建多进程的方式。
没有为Activity指定process属性,那么它运行在默认进程中,默认进程名为包名。
<activity
android:name=".ui.activity.loginregister.LoginActivity"
android:process=":remote"
android:screenOrientation="portrait" />
<activity
android:name=".ui.activity.loginregister.RegisterGuidActivity"
android:process="org.yqlee.demo.remote"
android:screenOrientation="portrait"/>
区别:
1、“:”含义是指要在当前的进程名前面附加上当前的包名,这是一种简写方式;对于RegisterGuidActivity中的声明方式,它是一种完整的命名方式,不会附加包名信息;
2、进程名以“:”开头的进程属于当前应用的私有进程,其他应用组件不可以和它跑在同一个进程中,而完整命名方式属于全局进程,其他应用可以通过SharedUID方式和它跑在同一个进程中。
2)、多进程模式的运行机制
简单说:当应用程序开启了多进程以后,各种奇怪的现象都出现了。
Android为每一个应用分配一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。
所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进程说带来的主要影响。比如:通过静态变量在内存中共享数据。
所以运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进程说带来的主要影响。
一般会造成以下一个方面的问题:
①、静态成员和单例模式完全失效
②、线程同步机制完全失效,不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象。
③、SharedPreferences的可靠性下降,SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这是因为SharedPreferences底层是通过读写XML文件实现的,并发写可能出现问题。
④、Application会多次创建
不同进程的组件的确会拥有独立的虚拟机、Application以及内存空间,这会给实际的开发带来很多困扰,是尤其需要注意的。
3、IPC基础概念介绍
本节主要介绍IPC中的一些基础概念,主要包含三个方面内容:Serializable接口、Parcelable接口以及Binder,只有熟悉这三方面内容以后,我们才能更好理解跨进程通信的各种方式。
Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需使用Parcelable或者Serialable。还有的时候我们需要把对象持久化到存储设备上或者通过网络给其他客户端,这时候也需要使用Serialable来完成对象的持久化
1)、Serializable接口
Serialazable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。
想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,实际上,设置这个serialVersionUID也不是必须的,不声明同样可以实现序列化,但是这将会对反序列化过程产生影响。
这个serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能正常地被反序列化。
serialVersionUID的详细工作机制是这样的:序列化时系统会把当前类的serialVersionUID写入序列化的文件中(也有可能是其他中介),当反序列化时系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类版本相同,这个时候可以成功反序列化。
一般来说,我们应该手动指定serialVersionUID的值,比如1L,也可以根据当前类结构自动生成它的hash值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常进行反序列化。如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这时候当前类的serialVersionUID就和序列化数据中的serialVersionUID不一致,于是反序列化失败,程序就会crash。
我们可以很明显感觉到serialVersionUID的作用,当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。
静态成员变量属于类而不属于对象,所以不会参与序列化过程;其次是transient关键字标识的成员不参与序列化过程。
2)、Parcelable接口
Parcelable接口内部包装了可序列化的数据,可在binder中只有传输,Parcelable主要用在内存序列化上,可以直接序列化的有Intent、Bundle、Bitmap、以及List和Map等等。
public class UserEntity implements Parcelable {
public Boolean em;
public Boolean frobid;
public Integer gender;
public String honor;
public Integer infotime;
public Boolean male;
public Boolean mb;
public Boolean normal;
public Boolean pending;
public Boolean qq;
public Boolean study;
public Integer studylevel;
public Boolean teach;
public Integer teachlevel;
public Boolean test;
public String user36id;
public Integer userid;
public String username;
public Boolean wx;
public UserEntity() {
}
protected UserEntity(Parcel in) {
honor = in.readString();
user36id = in.readString();
username = in.readString();
}
//实现反序列化操作
public static final Creator<UserEntity> CREATOR = new Creator<UserEntity>() {
//从序列化的对象中创建原始对象
@Override
public UserEntity createFromParcel(Parcel in) {
return new UserEntity(in);
}
//创建指定长度的原始对象数组
@Override
public UserEntity[] newArray(int size) {
return new UserEntity[size];
}
};
//内容描述,如果含有文件描述返回1,否则返回0,几乎所有情况下都返回0
@Override
public int describeContents() {
return 0;
}
//实现序列化操作,flags标识只有0和1,1表示标识当前对象需要作为返回值返回,
//不能立即释放资源,几乎所有情况都为0
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(honor);
dest.writeString(user36id);
dest.writeString(username);
}
}
既然Parcelable和Serializable都能实现序列化并且都可用于Intent间的数据传递,那么Serializable和Parcelable的区别:
①、Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量的IO操作。
②、Parcelable是Android中的序列化方式,因此更加适合Android平台上,它缺点就是使用起来稍微麻烦点(Android studio会自动填充),但它的效率很好,这是Android推荐的序列化方式,因此我们首选Parcelable。
3)、Binder
主观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android FrameWork角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等等)和相应ManagerService桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService时候,服务器会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,所以较为简单,无法触及Binder的核心,而Messenger的底层其实是AIDL,所以这里用AIDL来分析Binder机制。
4、android中的IPC方式
1)、使用Bundle
2)、使用文件共享
3)、使用Messenger
4)、使用AIDL
5)、使用ContentProvider
6)、使用Socket
5、Binder连接池
6、选用合适的IPC方式