Android Parcelable原理分析

前言

在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

与Serializable的区别

  1. Parcelable在对与只须要进行内存序列化的操做时很快,由于Serializable须要频繁的进行I/O。
  2. Parcelable实现较为复杂且要注意读写顺序的一致性,Serializable相对来讲实现很简单。
  3. Parcelable不适合用于作数据持久化,而Serializable适合。
相关文章
相关标签/搜索