什么是IPC?
在Android中如何进行多进程?
简单,在需要开启进程的Activity中声明一下即可,
使用android:process声明,引号中的内容是进程的名字,这个可以自己定义。
<activity
android:name=".SecondActivity"
android:process=":remote" >
</activity>
<activity
android:name=".ThirdActivity"
android:process="com.example.lu.myprocess.remote">
</activity>
多线程之间的通信:
新建三个Activity,每个布局中设置一个按钮跳转到下一个Activity,Activity中设置android:process。
在Eclipse中可以通过DDMS视图查看进程信息
在Android Studio中可以通过adb shell命令查看,具体命令是
adb shell ps | grep 你定义的进程名字
接下来我们讲解下多进程中的缺点和序列化,反序列化
多进程缺点
在JAVA的多进程中,从一个进程传递数据到另外一个进程,数据会发生改变,因为JAVA中是内存引用机制,数据存在内存块中,使用该数据实际上是引用存储数据的内存,在不同的进程中,同样数据的内存地址会不相同,这个可以通过下面代码测试出来。
新建一个类UserManger
public class UserManger{
public static int sUserId = 1;
}
然后在MainActivity中打印出来后再重新赋值为2,接着跳转到SecondActivity中再打印出sUserId的值,你会惊讶得发现打印出来的值是1。这是因为MainActivity和SecondActivity都运行在单独的进程中,Android为每一个进程分配了一个独立的虚拟机,不同的虚拟机在内存的分配上有不同的内存地址,这导致在不同的虚拟机中询问同一个类的对象会产生多份副本。所以就导致MainActivity中修改了sUserId的值,但是SecondActivity中的值没有改变。因此,所有运行在不同进程中的四大组件,只要他们之间需要通过内存共享数据,都会共享失败,这是多进程带来的主要影响。
一般来说,使用多进程会造成如下几方面的问题:
(1)静态成员和单例模式完全失效
(2)线程同步机制完全失效
(3)SharedPreferences的可靠性下降
(4)Application会多次创建
第一个问题答案为上述讲解的。第2个问题本质和第一个相同,既然不同内存,无论锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象。第3个问题因为SharedPreferences不支持两个进程同时执行写操作,不然会导致一定几率数据丢失,这是因为SharedPreferences底层是通过读/写XML文件来实现,并发写显然可能出现问题。第4个问题,当一个组件跑在新的进程中的时候,由于系统要创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程,相当于系统把这个应用重新启动了一遍,既然重新启动了,自然会创建新的Application。
序列化和反序列化
1 Serializable接口进行序列化和反序列化
Serializable是一个空接口,源码就不必看了。
Serializable实现起来比较简单无脑,直接实现Serializable接口,然后声明表示serialVersionUID就行了,
因为序列化的时候系统会把serialVersionUID写入序列化文件中,反序列化的时候系统会检测文件中的serialVerionUID,判断当前serialVersionUID和序列化的类的serialVersionUID是否相同,相同则可以反序列化,不同就会抛出错误,当序列化的类的成员变量的数量或者类型发生了改变也会无法正常反序列化。
我们应该手动指定serialVersionUID的值。
2 Parcelable接口进行序列化和反序列化(重点)
我们看下Parcelable的源码:
Parcelable接口实现起来稍微复杂点
新建一个UserManger类并实现Parcelable接口进行序列化。
package com.example.lu.myprocess;
import android.os.Parcel;
import android.os.Parcelable;
public class UserManger implements Parcelable {
private int userId;
private String userName;
private boolean isMale;
public UserManger(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public boolean isMale() {
return isMale;
}
public void setMale(boolean male) {
isMale = male;
}
protected UserManger(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readByte() != 0;
}
public static final Creator<UserManger> CREATOR = new Creator<UserManger>() {//完成反序列化功能
@Override
public UserManger createFromParcel(Parcel in) {
return new UserManger(in);
}
@Override
public UserManger[] newArray(int size) {
return new UserManger[size];
}
};
@Override
public int describeContents() {//完成内容描述功能
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {//完成序列化
dest.writeInt(userId);
dest.writeString(userName);
dest.writeByte((byte) (isMale ? 1 : 0));
}
}
parcel意思为包,邮包的意思,看方法意思可以理解为写到一个包去,就是打包,也就是完成序列化操作,毕竟序列化的概念就是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
我们看一下void writeToParcel(Parcel dest,int flags)的源码:
/**
* Flatten this object in to a Parcel.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written.
* May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
public void writeToParcel(Parcel dest, @WriteFlags int flags);
意思大概为:将该对象放到包中
参数dest:写入对象的包
参数flags:flags就是这两个(红色字体)
/**
* Flag for use with {@link #writeToParcel}: the object being written
* is a return value, that is the result of a function such as
* "<code>Parcelable someFunction()</code>",
* "<code>void someFunction(out Parcelable)</code>", or
* "<code>void someFunction(inout Parcelable)</code>". Some implementations
* may want to release resources at this point.
*/
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
返回值为1时,标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况为0.
看下方法int describeContents()的源码:
/**
* Describe the kinds of special objects contained in this Parcelable
* instance's marshaled representation. For example, if the object will
* include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
* the return value of this method must include the
* {@link #CONTENTS_FILE_DESCRIPTOR} bit.
*
* @return a bitmask indicating the set of special object types marshaled
* by this Parcelable object instance.
*/
public @ContentsFlags int describeContents();
大概意思是:描述包含在序列化的一些特殊的对象的种类。例如,如果对象将在{@link #writeToParcel(Parcel,int)}的输出中包含文件描述符,此方法的返回值必须包含{@link #CONTENTS_FILE_DESCRIPTOR}位。
(此处一脸懵B)返回值的意思是返回一个位掩码,表示由这个Parcelable对象实例封装的特殊对象类型集合。
咱们看下《Android开发艺术探索》中的描述
其实系统为我们提供了许多实现了Parcelable接口的类,它们都是可以直接实现序列化的,such as :Intent ,Bundle,Bitmap等,同时List和Map也可以序列化(需要它们的元素也是可序列化的)。