在Android中,一般使用序列化时,谷歌官方都推荐咱们使用
Parcelable
来实现,由于效率比jdk提供的Serializable
要高不少(大约10倍)。java
这里咱们首先先探讨一下Parcelable
怎么用,而后从源码出发解读Parcelable
的效率为何这么高。最后分析一下Parcelable
的应用场景,以及和Serializable
的区别。android
按照以下方式定义一个实现了Parcelable
的POJO对象,c++
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
对象的数据,ide
//传递
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的实现,咱们直接往下看,this
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
//经过指针强转拿到native层的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
对象从该段内存中读取对应的值的。作同理分析,以下,spa
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。指针
这也就是为何咱们在写序列化方法时,必定要将对应的成员变量的读取/写入顺序保持一致的缘由。rest
Parcelable
在对与只须要进行内存序列化的操做时很快,由于Serializable
须要频繁的进行I/O。Parcelable
实现较为复杂且要注意读写顺序的一致性,Serializable
相对来讲实现很简单。Parcelable
不适合用于作数据持久化,而Serializable适合。