Android 序列化 ---- Parcelable原理分析

        Parcelable是专门为Android设计的序列化接口。它是基于内存,底层是通过指针挪动实现。因此,执行效率比Java的Serializable要高的多,号称快10倍。

        一个简单的使用Parcelable进行序列化的例子:

public class ParcelObj implements Parcelable {
    private String strField;
    private int intField;

    // 需要提供一个接收 Parcel类型参数的构造函数,执行反序列化
    protected ParcelObj(Parcel in) {
        // 反序列化参数的顺序必须和序列化时保持一致
        this.strField = in.readString();
        this.intField = in.readInt();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    // 将一个对象转换成 Parcel对象,进行序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // 序列化参数的顺序必须和反序列化时保持一致
        dest.writeString(this.strField);
        dest.writeInt(this.intField);
    }

    // 实现类必须有一个CREATOR属性,用于反序列化,将Parcel对象转换为 Pacelable
    public static final Parcelable.Creator<ParcelObj> CREATOR = new             
                Parcelable.Creator<ParcelObj>() {
        // 反序列化的方法,将Parcel还原成Java对象
        @Override
        public ParcelObj createFromParcel(Parcel parcel) {
            return new ParcelObj(parcel);
        }

        @Override
        public ParcelObj[] newArray(int size) {
            return new ParcelObj[0];
        }
    };
}

        Parcelable序列化过程是通过Parcel来实现的。Parcel底层基于共享内存实现。 

C++共享内存原理

        Android NDK部分是用C/C++来实现(这里用C++来说明)。C++运行时内存分为栈区、堆区、全局区、代码区。其中堆区是C++存放对象的主要区域,在该区域分配的对象,使用完后必须要手动进行释放。否则,会造成内存泄漏。系统不会主动进行释放。这一块区域不在jvm运行时内存管理的范围。因此,所有用户进程都可以访问。

        C++提供了四种类型转换:const_cast、static_cast、dynamic_cast、reinterpret_cast。其中reinterpret_cast功能很强大。可以将对象类型指针转换为long类型的数。并且可以再次通过将该数值转换回对象指针,并可以访问对象。

// 在堆区分配对象
KPlayer * kPlayer = new KPlayer();
// 将对象指针转换为long类型数值
long playerValue = reinterpret_cast<long>(kPlayer); 


// (在另一个位置) 将刚才的数值,转换回对象指针(要求次对象没有被手动释放过)
KPlayer * kPlayer = reinterpret_cast<KPlayer *>(playerValue );
// 调用对象的方法
kellyPlayer->show();

        由此,共享内存的思路,即在Native层新建对象,并将指向对象的指针转换成数值返回Java层。Java层不同进程拿到的是相同的long类型数值,则可以访问Native层共同的对象。进而实现共享内存。

        Parcel序列化的原理,既是将java数据写入共享内存的对象,在另一个位置(或进程),读取数据并反序列化成对象。

Parcel源码分析

初始化

、        通过调用Parcel.obtain()方法,获取一个Parcel对象。当前没有缓存的Parcel对象则会新建一个。mNativePtr既是在上面提到的,在Native层对象指针返回给java层的数值。这个指针指向的是Native层的Parcel对象。

package android.os;

public final class Parcel {
    // 用于访问共享内存对象的long类型数值
    private long mNativePtr; // used by native code

    /**
     * 用于初始化 Parcel对象的函数
     * Retrieve a new Parcel object from the pool.
     */
    public static Parcel obtain() {
         ...
         return new Parcel(0);
         ...
    }
}

         new Parcel(0) 会调用到nativeCreate()函数。

private static native long nativeCreate();

private Parcel(long nativePtr) {
    if (DEBUG_RECYCLE) {
        mStack = new RuntimeException();
    }
    //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 {
        mNativePtr = nativeCreate();
        mOwnsNativeParcelObject = true;
    }
}

        nativeCreate()是一个本地方法。最终会调用到 android_os_Parcel.cpp的 nativeCreate。

        可以看到android_os_Parcel_create实际上就是创建一个对象,使用reinterpret_cast转换成long类型数值,返回。

// 这里是JNI的动态注册
static const JNINativeMethod gParcelMethods[] = {
    ...
    {"nativeCreate",   "()J", (void*)android_os_Parcel_create},
    ...
}

static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
    // 实例化一个Parcel对象
    Parcel* parcel = new Parcel();

    // 用reinterpret_cast讲Parcel转换jlong类型,并返回
    return reinterpret_cast<jlong>(parcel);
}

        在new Parcel()中会分配内存,初始化大小等。到此完成了Parcel的初始化。 

写数据

        以writeInt方法为例,writeInt也是调用native方法实现。

private static native void nativeWriteInt(long nativePtr, int val);

public final void writeInt(int val) {
        nativeWriteInt(mNativePtr, val);
}

         nativeWriteInt既是讲该int类型的值写入内存中,并将指针向后移动相应大小的位置。        

// android_os_Parcel.cpp:
...
{"nativeWriteInt",            "(JI)V", (void*)android_os_Parcel_writeInt},
...    

static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, 
        jlong nativePtr, jint val) {

    // 通过在Java层保存的,C++对象首地址,来查找到C++对象 Parcel* parcel
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        // 把内容写入进去
        const status_t err = parcel->writeInt32(val); 
        if (err != NO_ERROR) {
            signalExceptionForError(env, clazz, err);
        }
    }
}

// Parcel.cpp:
status_t Parcel::writeInt32(int32_t val)
{
    return writeAligned(val);
}

template<class T> 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: *reinterpret_cast<T*>(mData+mDataPos) = val;
        return finishWrite(sizeof(val));
    }
    ...
}


status_t Parcel::finishWrite(size_t len)
{
    mDataPos += len; // 根据数据大小,挪动指针

    if (mDataPos > mDataSize) {
        mDataSize = mDataPos;
    }
    
    return NO_ERROR;
}

        读数据过程与写数据一致,既是按照写的顺序和类型,一个一个的读出来。这也是为什么序列化写数据要和反序列化读数据的顺序要保持一致的原因。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值