前言
在Android中,通常使用序列化时,谷歌官方都推荐我们使用
Parcelable
来实现,因为效率比jdk提供的Serializable
要高很多(大约10倍)。
这里我们首先先探讨一下Parcelable
怎么用,然后从源码出发解读Parcelable
的效率为什么这么高。最后分析一下Parcelable
的应用场景,以及和Serializable
的区别。
如何使用
按照如下方式定义一个实现了Parcelable
的POJO对象,
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private int id;
private String name;
//setter & getter & constructor
//...
//下面是实现Parcelable接口的内容
//除了要序列化特殊的文件描述符场景外,一般返回零就可以了
@Override
public int describeContents() {
return 0;
}
//序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
//自定义的私有构造函数,反序列化对应的成员变量值
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
//根据反序列化得到的各个属性,生成与之前对象内容相同的对象
private Book(Parcel in) {
//切记反序列化的属性的顺序必须和之前写入的顺序一致!!
id = in.readInt();
name = in.readString();
}
}
以下代码展示了如何在Activity
之间通过序列化的方式传递Book
对象的数据,
//传递
Book book = new Book(123, "Hello world");
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("book_data", book);
startActivity(intent);
//接受
Book book = (Book) getIntent.getParcelableExtra("book_data);
分析源码
我们进入源码查看,Parcelable
只是一个接口,实现是交给Parcel
对象进行的。比如我们在writeToParcel
时会调用Parcel
类中的方法,进入其中可以看到实现是交给native做的,
...
public final void writeInt(int val) {
nativeWriteInt(mNativePtr, val);
}
...
@FastNative
private static native void nativeWriteInt(long nativePtr, int val);
@FastNative
private static native void nativeWriteLong(long nativePtr, long val);
@FastNative
private static native void nativeWriteFloat(long nativePtr, float val);
...
既然是native实现,就需要去看Android源码了。我们打开androidxref.com网站(可能需要fq,国内有个镜像站推荐一下aospxref.com),在Android源码中搜索nativeWriteInt
,定位到对应的c++实现位于目录frameworks/base/core/jni/android_os_Parcel.cpp
,有兴趣的朋友可以浏览一下。
Android源码里很多native方法都是动态注册的,这里不再赘述如何找到对应c的实现,我们直接往下看,
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
//通过指针再解释强转成Parcel对象
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
//最终还是调用的Parcel中的writeInt32函数
const status_t err = parcel->writeInt32(val);
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
}
}
...
//实际调用的是一个通用的模版方法
status_t Parcel::writeInt32(int32_t val)
{
return writeAligned(val);
}
...
//模版方法
//其中
//mData表示指向Parcel内存的首地址
//mDataPos表示指向Parcel空闲内存的首地址
//mDataCapacity表示Parcel分配内存的大小
template<class T>
status_t Parcel::writeAligned(T val) {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
//先判断加上val后会不会超过可用大小
if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
//reinterpret_cast是c++的再解释强制转换操作
//首先会计算mData + mDataPos得到物理地址,转成指向T类型的指针(T类型就是实际入参的类型)
//然后将val赋值给指针指向的内容
*reinterpret_cast<T*>(mData+mDataPos) = val;
//主要逻辑是修改mDataPos的偏移地址
//将偏移地址加上新增加的数据的字节数
return finishWrite(sizeof(val));
}
//如果超过了可用大小,执行增长函数
//之后再goto到上面的restart_write标签执行写入逻辑
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
通过上述代码分析,writeInt32
函数会将数据写入到一段共享的内存中,所以同理我们在readInt
时,也是通过Parcel
对象从该段内存中读取对应的值的。做同理分析,如下,
template<class T>
status_t Parcel::readAligned(T *pArg) const {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(T)) <= mDataSize) {
//获取读取数据的地址
const void* data = mData+mDataPos;
//mDataPos指向下一个数据
mDataPos += sizeof(T);
//根据数据指针类型取出数据
*pArg = *reinterpret_cast<const T*>(data);
return NO_ERROR;
} else {
return NOT_ENOUGH_DATA;
}
}
写数据时在一块Parcel内存地址中,写入12,34;读取数据时从起始地址(Android系统在读取时会将mDataPos重置为起始值)+指针类型对应的字节数来一一读取,首先读取12,然后读取34。
这也就是为什么我们在写序列化方法时,一定要将对应的成员变量的读取/写入顺序保持一致的原因。
与Serializable的区别
Parcelable
在对与只需要进行内存序列化的操作时很快,因为Serializable
需要频繁的进行I/O。Parcelable
实现较为复杂且要注意读写顺序的一致性,Serializable
相对来说实现很简单。Parcelable
不适合用于做数据持久化,而Serializable适合。