序列化方式
在Android开发过程中,需要对数据进行序列化操作时候 通常有两种方式
第一种 实现Serializable接口,第二种实现Parcelable接口,在writeToParcel方法中将数据写入,在createFromParcel读取数据,重新构造对象。
//第一种方式
public class Student implements Serializable
//第二种方式
public class Student implements Parcelable {
protected Student(Parcel in) {
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
}
Serializabale接口方式 开发简单,只要实现接口就行。但通常为了性能考虑,会选择parcelable接口进行序列化处理。那么为什么Parcelable接口的性能要比Serializabale接口优秀呢,简单来说,Serializable接口是基于IO流的操作,而Parcelable是基于内存的操作。可想而知,设想一下 一个是从磁盘中读取数据,一个是从内存中读取数据,哪个更快就一目了然了。
接下来就通过源码分析Parcel共享内存的实现原理
先看Parcel的代码,有一个变量mNativePtr,它是由底层创建并返回给java层的一个指针,代表共享内存的首地址
public final class Parcel {
private static final boolean DEBUG_RECYCLE = false;
private static final boolean DEBUG_ARRAY_MAP = false;
private static final String TAG = "Parcel";
@SuppressWarnings({"UnusedDeclaration"})
private long mNativePtr; // used by native code
创建过程 调了nativeCreate方法,我们跟进去看
private void init(long nativePtr) {
if (nativePtr != 0) {
mNativePtr = nativePtr;
mOwnsNativeParcelObject = false;
} else {
mNativePtr = nativeCreate();
mOwnsNativeParcelObject = true;
}
}
//进到了jni函数内部
private static native long nativeCreate();
我们找到Android源码,在android_os_Parcel.cpp种可以找到方法的具体实现。这段代码也很简单,构造了一个Parcel指针,并将它返回出去
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
Parcel* parcel = new Parcel();
return reinterpret_cast<jlong>(parcel);
}
在java层中 我们调用writeInt方法 其实是将int值写入到共享内存中,那么看看具体是怎么操作

我们可以看到 是将java传的指针对象强转成C层的指针对象,并通过该对象 去执行写入的操作。具体看一下Parcel.cpp的writeInt32的方法。在该方法中 将内存的指针 根据所传值的内存大小,将指针进行偏移,在偏移到指定位置后,再将数据写入该内存地址中。
status_t Parcel::writeInt32(int32_t val)
{
return writeAligned(val);
}
status_t Parcel::writeAligned(T val) {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
//如果内存大小还没超出阈值
if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
//内存指针偏移 mdataPos位 并将偏移后的内存进行赋值
*reinterpret_cast<T*>(mData+mDataPos) = val;
return finishWrite(sizeof(val));
}
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
在存完之后,我们其实很容易也难猜到,怎么取数据,只要从指定内存位置中去取出来就可以了。接下来看一下readInt32方法是怎么读取数据。
但在这之前,经过一系列的写入数据,指针的偏移量已经不再是0了,所以为了按顺序读取数据,需要将偏移量置为0,让指针从首地址开始,按照写入数据的顺序,依次读出。




将指针对应的值返回出来。
通过对内存的操作,实现了数据存取 即序列化的过程
下一篇 手动实现共享内存NDK开发之路2-手动实现Parcel
网友评论