首先,本篇内容的分析以及涉及到的Android源码都是基于android-6.0.1_r1分支的。
把对象转化为字节序列的过程叫序列化,反之把字节序列恢复成对象叫反序列化。Parcelable对象主要用于内存变量,是为了Android不同组件间高效传输数据而设计的,而Serializable的作用是为了保存对象的属性到本地文件,数据库,网络流,RMI以方便数据传输,因为其序列化使用了反射,且会生成大量的临时变量(会造成频繁的GC),故效率要相对慢。
`Serializable`是Java的一个标志接口。其是一个空接口,内容如下
```
public interface Serializable {
}
```
其通常用法是,实现`Serializable`接口,通过`ObjectInputStream`和`ObjectOutputStream`进行对象的读写。
Serializable序列化浅析,参考了Java序列化高级认识。
Serializable序列化ID
虚拟机是否允许反序列化,不仅取决于类的路径和代码功能是否一致,一个非常重要的要点是:两个类的序列化ID是否一致,也就是 private statiac final long serialVersionUID = 1L
,该值的存在是为了限制一些对序列化文件的访问,默认设置为1L就好。
静态变量的Serializable
序列化
序列化并不保存静态变量的,因为序列化保存的是对象的状态,而静态变量属于类的状态。
父类的序列化
若父类未实现Serializable
,子类实现了Serializable
接口,序列化子类的时候,虚拟机是不会序列化父类的,但是在反序列化子类的时候,为了构造父类,必须调用父类的无参构造函数(父类必须提供无参构造函数),来先创建父类,再创建子类,此时父类的对象被初始化为初始值(如int的0,对象的null等). 当然,可以考虑在无参构造函数中,对反序列化的父类变量进行初始化。
Transient
关键字
Transient
可以使修饰的字段不被序列化到文件中,反序列化的时候,该变量的值被设定为初始值(如int
的0,对象的null
等)。当然,可以利用1.3中,把不需要初始化的属性放在父类,父类不实现Serializable
接口,达到同样的效果。
对敏感字段加密
在序列化过程中,虚拟机会试图调用对象类里面的writeObject
和readObject
方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则调用默认的ObjectOutputStream
的defaultWriteObject
方法和ObjectInputStream
的defaultReadObject
方法。基于这个原理,可以实现对敏感字段加密。
序列化存储规则
Java序列化机制为了节省磁盘空间,当写入文件的为同一对象时,并不会再将内容进行存储,只是再次存储一份引用,反序列化的时候恢复引用关系,使这两个引用指向同一个对象。此处有一点需要注意的是:即使在第二次写入这个对象之前,改变了对象中属性的值,仍然只是保存一个对象,反序列化的时候读取到的值,也只是第一次写入的值。
Parcelable是Android考虑Serializable效率不高而自己开发的一套序列化机制。用于Bundle(Bundle实现了Parcelable接口)传值,AIDL通讯等方面。Parcelable是IBinder通讯的消息载体。
public interface Parcelable {
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
//内容描述接口,返回一个掩码,表示一组特殊对象类型的Parcelable。
public int describeContents();
//写入函数接口
public void writeToParcel(Parcel dest, int flags);
//读取接口,目的是从Parcel中构造一个实现了Pacelable的类的实例。
//这里用到了模板方法,继承类名通过模板参数传入,为能够实现模板参数传入,这里定义了Creator嵌入接口,内含两个函数分别返回单个和多个继承实例。
public interface Creator<T> { public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
public interface ClassLoaderCreator<T> extends Creator<T> {
public T createFromParcel(Parcel source, ClassLoader loader);
}
}
必须实现Creator接口,CREATOR用于从Parcel中实例化你的包装类。该接口有两个方法:
createFromParcel:实现从in中创建出类的实例的功能。
newArray:创建类的实例数组。
在读取Parcelable out和in对应的属性顺序不能错,否则取不到值。
ClassLoaderCreator<T> : 专业化的Creator,允许你接收的Object内部创建的ClassLoader对象。
writeToParcel(Parcel dest, int flags):
Parcel文件位于`frameworks/base/core/java/android/os/Parcel.java,应用程序可以通过Parcel.obtain()获取一个Parcel对象。
private static final int POOL_SIZE = 6;
private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
public static Parcel obtain() {
final Parcel[] pool = sOwnedPool;
synchronized (pool) {
Parcel p;
for (int i=0; i<POOL_SIZE; i++) {
p = pool[i];
if (p != null) {//引用不为null表示可用
//引用置为null,这样下次就知道这个Parcel被占用了。
pool[i] = null;
return p;
}
}
}
return new Parcel(0);
}
这里,获取对象的时候,首先是是从系统默认的大小为6的sOwnedPool中查询有没有未使用的,若是池中有多余的则直接使用,并且将其使用标志置为null,表示已被占用。否则若没有多余的则调用new Parcel(0)
创建一个新的Parcel。
以下是创建Parcel的构造方法,参数nativePtr = 0
,最终调用了Native层的nativeCreate方法。
private Parcel(long nativePtr) {
//Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
init(nativePtr);
}
private void init(long nativePtr) {
if (nativePtr != 0) {
mNativePtr = nativePtr;
mOwnsNativeParcelObject = false;
} else { //传值为0的时候创建新的
mNativePtr = nativeCreate();
mOwnsNativeParcelObject = true;
}
}
nativeCreate是Native层的android_os_Parcel_create
方法。其实现位于frameworks/base/core/jni/android_os_Parcel.cpp
中,如下:
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
Parcel* parcel = new Parcel();
return reinterpret_cast<jlong>(parcel);
}
这里,将Native层生成的Parcel对象指针,通过reinterpret_cast<jlong>
,转换为jlong类型,保存在Java变量mNativePtr中。即mNativePtr中保存的是Native层Parcel的指针。
reinterpret_cast<>
用于处理类型无关的类型之间的强制转换。这里用来将指针parcel转换为long,其原理具体分析可以参考C++强制类型转换
下面我们来看看Parcel的构造实现,其源码位于frameworks/native/libs/binder/Parcel.cpp
:
Parcel::Parcel()
{
initState();
}
void Parcel::initState()
{
mError = NO_ERROR; //错误码
mData = 0; //Parcel中存储的数据,它使一个uint8_t*类型的指针
mDataSize = 0; //Parcel中已存储的数据大小
mDataCapacity = 0; //最大存储能力
mDataPos = 0; //数据指针
mObjects = NULL;
mObjectsSize = 0;
mObjectsCapacity = 0;
mNextObjectHint = 0;
mHasFds = false;
mFdsKnown = true;
mAllowFds = true;
mOwner = NULL;
mBlobAshmemSize = 0;
}
Parcel对象的构造过程只是简单的初始化了对象的属性。这里是因为Parcel设计的时候遵循了“动态扩展”原则,即使用的时候再申请内存避免浪费资源。
writeString16最终都是调用以下方法来实现写数据。
status_t Parcel::writeString16(const char16_t* str, size_t len)
{
if (str == NULL) return writeInt32(-1);//str为空的时候,写入-1;
status_t err = writeInt32(len); //先写入长度
if (err == NO_ERROR) {
len *= sizeof(char16_t); //占用空间大小
uint8_t* data = (uint8_t*)writeInplace(len+sizeof(char16_t));
if (data) {//
memcpy(data, str, len); //将数据str复制到data所指位置。
*reinterpret_cast<char16_t*>(data+len) = 0;//写入字符串结束符0
return NO_ERROR;
}
err = mError;
}
return err;
}
以上通过writeInplace获取到数据写入位置,然后通过memcpy拷贝数据。以下分析获取数据写入位置的实现。
void* Parcel::writeInplace(size_t len)
{
if (len > INT32_MAX) { //超出范围或者负数直接返回
return NULL;
}
const size_t padded = pad_size(len);//页对齐。
if (mDataPos+padded < mDataPos) { //padded为负数,返回
return NULL;
}
if ((mDataPos+padded) <= mDataCapacity) {//没有超过最大容量
restart_write:
uint8_t* const data = mData+mDataPos; //当前地址加偏移
if (padded != len) { //需要填充尾部的情况
#if BYTE_ORDER == BIG_ENDIAN //大端
static const uint32_t mask[4] = {
0x00000000, 0xffffff00, 0xffff0000, 0xff000000
};
#endif
#if BYTE_ORDER == LITTLE_ENDIAN //小端
static const uint32_t mask[4] = {
0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff
};
#endif
//使用mask中的1,填充尾部
*reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len];
}
finishWrite(padded);//更新mDataPos,指向最新位置。
return data;
}
status_t err = growData(padded); //内存不够,新增空间
if (err == NO_ERROR) goto restart_write;
return NULL;
}
status_t Parcel::growData(size_t len)
{
if (len > INT32_MAX) {
return BAD_VALUE;
}
size_t newSize = ((mDataSize+len)*3)/2;
return (newSize <= mDataSize)
? (status_t) NO_MEMORY
: continueWrite(newSize);
}
读取部分最终调用的是readString16Inplace,
const char16_t* Parcel::readString16Inplace(size_t* outLen) const
{
int32_t size = readInt32(); //首先读取长度
if (size >= 0 && size < INT32_MAX) { //判断读取到的长度是否正常
*outLen = size;
const char16_t* str = (const char16_t*)readInplace((size+1)*sizeof(char16_t));
if (str != NULL) {
return str;
}
}
*outLen = 0;
return NULL;
}
这里调用了readInplace 来获取读取的位置,分析如下:
const void* Parcel::readInplace(size_t len) const
{
if (len > INT32_MAX) {//长度不正常,直接返回NULL
return NULL;
}
if ((mDataPos+pad_size(len)) >= mDataPos && (mDataPos+pad_size(len)) <= mDataSize
&& len <= pad_size(len)) {
const void* data = mData+mDataPos;//获取数据位置
mDataPos += pad_size(len);//更新mDataPos
ALOGV("readInplace Setting data pos of %p to %zu", this, mDataPos);
return data;
}
return NULL;
}
obtain 从池中获取一个Parcel.
dataSize 得到当前Parcel对象的实际存储空间
dataCapacity 得到当前Parcel对象已分配的存储空间。
dataPostion 获取当前Parcel对象的偏移值,类似文件流指针的偏移值
setDataPostion 设置当前Parcel对象的偏移值。
recyle 清空,回收当前Parcel对象的内存
writeXxx 向当前Parcel对象写入数据,具有多重重载
readXxx 从当前Parcel对象读取数据,具有多重重载
见github