在看Parcelable之前需要先了解一下Parcel。
Parcel
Parcel提供了一套机制,就是可以将序列化之后的数据写入到一块共享内存中,然后别的进程可以通过Parcel从这块共享内存中读出字节流,并反序列化成一个对象。
Parcel在内存中的结构是一块连续的内存,会根据需要自动扩展大小。
从上图就可以看出来,这个Parcel就是一个消息容器,消息就是数据和对象引用,这些对象信息通过IBinder来传输。
因为Parcel是直接使用内存来读取数据,所以它有这样的特点?
1. Parcel是序列化,但不是持久序列化(Serializable是持久序列化);
2. Parcel主要目的是提高IPC时的数据传输性能。
Parcel可以用到很多地方(用处)?
1. Binder IPC 中客户端和服务器端的数据交互;
2. AIDL数据交互。
Parcel这个消息容器可以包含哪些数据呢?
1. 小型数据:从用户空间(源进程)复制到kernel空间(Binder驱动中)再写回用户空间(目标进程,binder驱动负责寻找目标进程)
2. 大型数据:使用android 的匿名共享内存传递
3. binder对象:Kernel binder驱动专门处理
Parcelable
Parcelable是一个可以使任意类对象具有序列化能力的接口,凡是实现了这个接口的类对象,都可以被打包进Parcel实例中,使得IPC变得更加高效。
关于如何来使用Parcelable,需要实现以下几个步骤:
1. 实现Parcelable接口;
public class Girl implements Parcelable
2. 实现接口中的两个方法(describeContents 和 writeToParcel);
describeContents()方法
@Override
public int describeContents() {
return 0;
}
用来实现内容描述功能,几乎在任何情况下,这个方法都返回0,仅当对象中存在文件描述符时,此方法返回1(标记位:CONTENTS_FILE_DESCRIPTOR)。
像 fileInputStream.getFD() 是获取文件描述符对象。
writeToParcel(Parcel dest, int flags)方法
@Override
public void writeToParcel(Parcel parcel, int i) {
}
用来实现序列化功能,其最终的实现是Parcel的一系列write方法。其目的是将当前对象写入序列化结构中,当flags为1时,代表当前对象需要作为返回值返回,但几乎所有情况都为0(标记位:PARCELABLE_WRITE_RETURN_VALUE)。
3. 实现静态内部类对象CREATOR。
public static final Creator<Girl> CREATOR = new Creator<Girl>()
用来实现反序列化功能。其内部标明了如何反序列化对象和数组,并通过Parcel的一系列read方法完成反序列化。
Parcelable用法
根据上面说的Parcelable的方法,我们可以在使用的时候按这个顺序,一步步实现:
1. 先准备好一个对象,这个类实现Parcelable,并实现他的set/get方法。
public class Girl implements Parcelable {
private String name;
private int age;
private boolean isMarried;
public Girl(String neame,int age,boolean isMarried){
this.name = name;
this.age = age;
this.isMarried = isMarried;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isMarried() {
return isMarried;
}
public void setMarried(boolean married) {
isMarried = married;
}
}
2. 重写 describeContents() 方法,对象中一般没有文件描述符,所以并返回一个0。
/**
* 描述
*/
@Override
public int describeContents() {
return 0;
}
3. 重写 writeToParcel(Parcel parcel, int i) 方法,将数据依次序列化。
/**
* 将数据分别用write一个个依次序列化
* 其中,parcel没有提供直接的方法来序列化boolean类型的数据,
* 所以,需要将boolean类型转化成byte,再用writeByte序列化
*/
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeInt(age);
parcel.writeByte((byte)(isMarried ? 1 : 0));
}
4. 在构造方法中获取序列化数据,通过read依次读取数据。
/**
* 在构造方法中获取序列化数据
* 读取变量的顺序必须和之前写入变量的顺序保持一致;
* 如果调换顺序,拿出的数据会被赋值到错误的变量
*/
public Girl(Parcel parcel){
name = parcel.readString();
age = parcel.readInt();
isMarried = (int)parcel.readByte() == 1;
}
5. 实现实现接口Parcelable.Creator。
/**
* createFromParcel和newArray
* 两者都是调用 new Girl() 来读取数据,
* 只是一个是对象,一个是数组
*/
public static final Creator<Girl> CREATOR = new Creator<Girl>() {
/**
* 读取序列化对象
* @return
*/
@Override
public Girl createFromParcel(Parcel parcel) {
return new Girl(parcel);
}
/**
* 读取序列化数组
* @return
*/
@Override
public Girl[] newArray(int size) {
return new Girl[size];
}
};
测试:
根据以上写的五个步骤,序列化和反序列就大功告成了,接下来就是测试一下这个已经实现的Girl类。
我们知道,在两个activity中传递的时候,对象是需要序列化的,所以我们就用两个activity传递一下这个Girl类,看看能否正确的传递过去。
发送方:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, SendActivity.class);
intent.putExtra("key", new Girl("seas",97,false));
startActivity(intent);
}
});
接收方:
Intent intent = getIntent();
Girl girl = intent.getParcelableExtra("key");
Log.e("ParcelableTest", girl.getName() + girl.getAge() + girl.isMarried());
结果:
我们是在发送方给一个对象赋值并发给接收方,接收方打开这个传过来的对象,发现值是正确的,成功。
参考文献
Parcel
https://www.jianshu.com/p/fc7f7d1b0551
Parcelable
https://www.jianshu.com/p/116fce3e78c6
https://blog.youkuaiyun.com/bzlj2912009596/article/details/81091919
https://blog.youkuaiyun.com/a2241076850/article/details/70821153
文件描述符
https://www.jianshu.com/p/837320d6faea
《Android 开发艺术探索》P45