IPC不是android中独有的,任何一个操作系统都需要相应的IPC机制,比如Window上可以通过剪贴板,管道和邮槽来进行进程间通信;Linux上可以通过命名管道、共享内存、信号量等进行进程间通信,可以看到不同的操作系统平台有着不同的进程间通信方式。对于Android来说,它是一种基于Linux内核的移动操作系统,它的进程间通信方式不能完全继承自LInux,相反,它有自己的进程间通信方式,最有特色的进程通信方式就是Binder了,通过Binder可以轻松实现进程间通信,除了Binder,Android还支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然,一个设备上的两个进程通过Socket通信自然也是可以的。
IPC是Inter-Process Communication的缩写,含义是进程间通信或者跨进程通信,具体指两个进程之间进行数据交换的过程。首先得要了解什么事进程,什么事线程。进程和线程是截然不同的概念。
线程是cpu调度的最小单元,同时线程是一种有限的系统资源。进程一般指一个执行单元,在PC和移动设备上指一个程序或者应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。在最简单的情况下,一个进程可以只有一个线程,即主线程,在Android里面的主线程也叫UI线程,在UI线程里才能操作界面元素。很多时候,一个进程中需要执行大量的耗时任务,如果这些任务放在主线程中去执行就会造成界面无响应,严重影响用户体验,这种情况在PC系统和移动系统中都存在,在Android中有特殊的名字叫做ANR,即应用无响应,解决这个问题就需要用到线程,把一些耗时的任务放在线程中。
Android中的多进程
如何在Android开启多进程呢?首先,在Android中使用多进程只有一种方法,那就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidMenifest中指定android:process属性,除此之外没有其他办法,也就是我们无法给一个线程或者一个实体类指定其运行时所在的进程。其实还有另一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程,但是这种属于特殊情况,也不是常用的创建多进程的方式。
<activity
android:name="com.yy.demo.SecondActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process=":remote" />
<activity
android:name="com.yy.demo.ThridActivity"
android:configChanges="screenLyout"
android:label="@string/app_name"
android:process="com.yy.demo.remote"/>
上面的代码分别为SecondActivity和ThirdActivity指定了process属性,并且它们的属性值不同,这意味着当前应用又增加了两个新进程。其中注意到SecondActivity和ThirdActivity的android:process属性分别为“:remote”和“com.yy.demo.remote”.这两种方式是有区别的,首先“:”的含义是指在当前的进程名前面附加上当前的包名,这是一种简写的方法,对于SecondActivity来说,它完整的进程名为com.yy.demo.remote,这是一种完整的命名方式,不会附加包名信息;其次,进程名以:“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方式和它跑在同一个进程中。
Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据,两个应用通过·ShareUID跑在同一个进程是有要求的,需要两个应用有相同的ShareUID并且签名才可以。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息,还可以共享内存数据,看起来就像是一个应用的两个部分。
所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进程所带来的主要影响。正常情况下四大组件中间不可能不通过一些中间层来共享数据,那么通过简单地指定进程名来开启进程都会无法正确运行。当然,在特殊情况下,某些组件之前不需要共享数据,这个时候可以直接指定android:process属性来开启多进程,但是这种场景是不常见的,几乎所有情况都需要共享数据。
一般来说,使用多进程会造成如下几方面的问题:
1.静态成员和单例模式完全失效。
2.线程同步机制完全失效
3.SharePreferences的可靠性下降
4.Application会多次创建
第一个问题上面已经进行分析,第2个问题本质上和第一个问题类似的,既然都不是一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的对象不是同一个对象,第3个问题是因为SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这是因为SharedPrefences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这是因为SharedPreferences底层是通过读/写XML文件实现的,并发写显然是可能出问题的,甚至并发读/写都有可能出问题。第4个问题也是显而易见的,当一个组件跑在一个新的进程中的时候,由于系统要在创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。因此,相当于系统又把这个应用重新启动了一遍,既然重新启动了,那么自然会创建新的Application。这个问题其实可以这么理解,运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的,同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application的。
IPC基础概念
public class MyParcelable implements Parcelable {
// You can include parcel data types
private int mData;
private String mName;
// We can also include child Parcelable objects. Assume MySubParcel is such a Parcelable:
private MySubParcelable mInfo;
// This is where you write the values you want to save to the `Parcel`.
// The `Parcel` class has methods defined to help you save all of your values.
// Note that there are only methods defined for simple values, lists, and other Parcelable objects.
// You may need to make several classes Parcelable to send the data you want.
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
out.writeString(mName);
out.writeParcelable(mInfo, flags);
}
// Using the `in` variable, we can retrieve the values that
// we originally wrote into the `Parcel`. This constructor is usually
// private so that only the `CREATOR` field can access.
private MyParcelable(Parcel in) {
mData = in.readInt();
mName = in.readString();
mInfo = in.readParcelable(MySubParcelable.class.getClassLoader());
}
public MyParcelable() {
// Normal actions performed by class, since this is still a normal object!
}
// In the vast majority of cases you can simply return 0 for this.
// There are cases where you need to use the constant `CONTENTS_FILE_DESCRIPTOR`
// But this is out of scope of this tutorial
@Override
public int describeContents() {
return 0;
}
// After implementing the `Parcelable` interface, we need to create the
// `Parcelable.Creator<MyParcelable> CREATOR` constant for our class;
// Notice how it has our class specified as its type.
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
// This simply calls our new constructor (typically private) and
// passes along the unmarshalled `Parcel`, and then returns the new object!
@Override
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
// We just need to copy this and change the type to match our class.
@Override
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
}
Parcelable和Serizable都能实现序列化都可以用于Intent间的数据传递,Serializable是java中的序列化接口,使用起来简单但是开销大,Parceable是android中的序列化方式,使用稍微麻烦,但是效率高。下面通过一种图讲诉Binder的工作机制: