Java:深入理解Serializable和Parcelable

39 篇文章 0 订阅
7 篇文章 0 订阅


前言

这篇文章是笔者在学习Serializable和Parcelable两处知识点针对自己疑问去探索的学习纪录

一、笔者产生的问题

为什么Java实现了Serializable对象才能意识到这是支持序列化的?
Serializable为什么效率上不如Parcelable?

二、源码解读

Serializable相关

众所周知,Serializable是个空接口,要进行序列化与反序列化的类实现这个接口之后,主要通过ObjectInputStream和ObjectOutputStream进行序列化与反序列化,如果没有实现Serializable或者其他相关接口则会报错。
那Serializable的源码几近空空如也,我们不妨从ObjectInputStream和ObjectOutputStream源码进行下手解读
先来看ObjectOutputStream的源码

先把使用的代码贴上,方便后续阅读

	try {
            //输出序列化
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            //输入反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            person = (Person) ois.readObject();
            return person;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }

源码入口:

/**
     * Creates an ObjectOutputStream that writes to the specified OutputStream.
     * This constructor writes the serialization stream header to the
     * underlying stream; callers may wish to flush the stream immediately to
     * ensure that constructors for receiving ObjectInputStreams will not block
     * when reading the header.
     *
     * <p>If a security manager is installed, this constructor will check for
     * the "enableSubclassImplementation" SerializablePermission when invoked
     * directly or indirectly by the constructor of a subclass which overrides
     * the ObjectOutputStream.putFields or ObjectOutputStream.writeUnshared
     * methods.
     *
     * @param   out output stream to write to
     * @throws  IOException if an I/O error occurs while writing stream header
     * @throws  SecurityException if untrusted subclass illegally overrides
     *          security-sensitive methods
     * @throws  NullPointerException if {@code out} is {@code null}
     * @since   1.4
     * @see     ObjectOutputStream#ObjectOutputStream()
     * @see     ObjectOutputStream#putFields()
     * @see     ObjectInputStream#ObjectInputStream(InputStream)
     */
    public ObjectOutputStream(OutputStream out) throws IOException {
        verifySubclass();
          // bout表示底层的字节数据容器
        bout = new BlockDataOutputStream(out);
        handles = new HandleTable(10, (float) 3.00);
        subs = new ReplaceTable(10, (float) 3.00);
        enableOverride = false;
        writeStreamHeader(); // 写入文件头
        bout.setBlockDataMode(true);// flush数据
        if (extendedDebugInfo) {
            debugInfoStack = new DebugTraceInfoStack();
        } else {
            debugInfoStack = null;
        }
    }

补:常见的ByteArrayOutputStream和FileOutputStream都是继承自OutputStream,所以传入前面的两个流作为参数的构造函数源码便是以上呈现的。

这个构造函数首先将out对象绑定到bout

/**
         * Creates new BlockDataOutputStream on top of given underlying stream.
         * Block data mode is turned off by default.
         */
        BlockDataOutputStream(OutputStream out) {
            this.out = out;
            dout = new DataOutputStream(this);
        }

然后调用writeStreamHeader()方法

	/**
     * The writeStreamHeader method is provided so subclasses can append or
     * prepend their own header to the stream.  It writes the magic number and
     * version to the stream.
     *
     * @throws  IOException if I/O errors occur while writing to the underlying
     *          stream
     */
    protected void writeStreamHeader() throws IOException {
        bout.writeShort(STREAM_MAGIC);
        bout.writeShort(STREAM_VERSION);
    }

		/**
	 * Magic number that is written to the stream header. 
	 */
	final static short STREAM_MAGIC = (short)0xaced;
	
	/**
	 * Version number that is written to the stream header. 
	 */
	final static short STREAM_VERSION = 5;

接下来调用writeObject()

public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }

很明显看到我们之前的代码里初始化的时候enableOverride会被设置成false,所以这里会调用writeObject0方法,我们继续往下看

	/**
     * Underlying writeObject/writeUnshared implementation.
     */
    private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // handle previously written and non-replaceable objects
            int h;
            if ((obj = subs.lookup(obj)) == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }

            // check for replacement object
            Object orig = obj;
            //获取要进行序列化的对象
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                //检验了是否有重写入的方法
                Class<?> repCl;
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }

            // if object replaced, run through original checks a second time
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }

            // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
            	//实现了Serializable接口的类,进行以下的方法
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

嚯,总归是看到我们分析的主角了,从这里我们就可以看到,如果没有实现Serializable方法,那么便不会调用writeOrdinaryObject方法,而是会往下抛出异常
接下来看看writeOrdinaryObject()方法

/**
     * Writes representation of an "ordinary" (i.e., not a String, Class,
     * ObjectStreamClass, array, or enum constant) serializable object to the
     * stream.
     */
    private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                (depth == 1 ? "root " : "") + "object (class \"" +
                obj.getClass().getName() + "\", " + obj.toString() + ")");
        }
        try {
        	// 写入Object标志位
            desc.checkSerialize();

            bout.writeByte(TC_OBJECT);
            // 写入类元数据
            writeClassDesc(desc, false);
            handles.assign(unshared ? null : obj);

            if (desc.isRecord()) {
                writeRecordData(obj, desc);
            } else if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);// 写入被序列化的对象的实例数据
            } else {
                writeSerialData(obj, desc);
            }
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }

	/**
	 * new Object.
	 */
	final static byte TC_OBJECT =       (byte)0x73;

接下来会调用writeClassDesc()方法写入被序列化对象的类的类元数据,writeClassDesc()方法实现如下:

private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    int handle;
    if (desc == null) {
        // 如果desc为null
        writeNull();
    } else if (!unshared && (handle = handles.lookup(desc)) != -1) {
        writeHandle(handle);
    } else if (desc.isProxy()) {
        writeProxyDesc(desc, unshared);
    } else {
        writeNonProxyDesc(desc, unshared);
    }
}

一般情况下接下来会调用writeNonProxyDesc()方法,该方法实现如下:

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
    throws IOException
{
    // TC_CLASSDESC =    (byte)0x72;
    // 表示一个新的Class描述符
    bout.writeByte(TC_CLASSDESC);
    handles.assign(unshared ? null : desc);
 
    if (protocol == PROTOCOL_VERSION_1) {
        // do not invoke class descriptor write hook with old protocol
        desc.writeNonProxy(this);
    } else {
        writeClassDescriptor(desc);
    }
 
    Class cl = desc.forClass();
    bout.setBlockDataMode(true);
    if (cl != null && isCustomSubclass()) {
        ReflectUtil.checkPackageAccess(cl);
    }
    annotateClass(cl);
    bout.setBlockDataMode(false);
    bout.writeByte(TC_ENDBLOCKDATA);
 
    writeClassDesc(desc.getSuperDesc(), false);
}

writeNonProxyDesc调用writeNonProxy()最终把数据完成写入啦

void writeNonProxy(ObjectOutputStream out) throws IOException {
    out.writeUTF(name); // 写入类的名字
    out.writeLong(getSerialVersionUID()); // 写入类的序列号
 
    byte flags = 0;
    // 获取类的标识
    if (externalizable) {
        flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
        int protocol = out.getProtocolVersion();
        if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
            flags |= ObjectStreamConstants.SC_BLOCK_DATA;
        }
    } else if (serializable) {
        flags |= ObjectStreamConstants.SC_SERIALIZABLE;
    }
    if (hasWriteObjectData) {
        flags |= ObjectStreamConstants.SC_WRITE_METHOD;
    }
    if (isEnum) {
        flags |= ObjectStreamConstants.SC_ENUM;
    }
    out.writeByte(flags); // 写入类的flag
 
    out.writeShort(fields.length); // 写入对象的字段的个数
    for (int i = 0; i < fields.length; i++) {
        ObjectStreamField f = fields[i];
        out.writeByte(f.getTypeCode());
        out.writeUTF(f.getName());
        if (!f.isPrimitive()) {
            // 如果不是原始类型,即是对象或者Interface
            // 则会写入表示对象或者类的类型字符串
            out.writeTypeString(f.getTypeString());
        }
    }
}

从这里我们也可以看到,Serilizable的工作几乎都是在Java层完成的,输入输出也是采用的流的形式,产生了大量的I/O操作,过程中产生了大量的对象产生和消除,这必然会引起GC机制降低效率,并且用到了反射机制,这一定程度上也降低了其的效率。

Parcelable相关

Parcelable与Serializable相关,只是它是针对连续内存进行操作
Parcelable严格意义上来说是Android SDK中的一个API,它的序列化是基于Native层进行实现的,不同版本的API可能实现方式有所不同
先来看看Parcelable的源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package android.os;

public interface Parcelable {
    int CONTENTS_FILE_DESCRIPTOR = 1;
    int PARCELABLE_WRITE_RETURN_VALUE = 1;

    int describeContents();

    void writeToParcel(Parcel var1, int var2);

    public interface Creator<T> {
        T createFromParcel(Parcel var1);

        T[] newArray(int var1);
    }

    public interface ClassLoaderCreator<T> extends Parcelable.Creator<T> {
        T createFromParcel(Parcel var1, ClassLoader var2);
    }
}

然后我们发现,其实比较关键的两个函数都有Parcel的身影,似乎很多工作都是Parcel完成的,Parcelable只是一个壳子,于是我们继续看Parcel,这里我们看一个writeInt方法

/**
     * Write an integer value into the parcel at the current dataPosition(),
     * growing dataCapacity() if needed.
     */
    public final void writeInt(int val) {
        int err = nativeWriteInt(mNativePtr, val);
        if (err != OK) {
            nativeSignalExceptionForError(err);
        }
    }
	
	@CriticalNative
    private static native int nativeWriteInt(long nativePtr, int val);

发现是个Native方法,okk,看Native层
笔者定位到了android_os_Parcel.cpp,原谅我没找到具体C的实现,这里借鉴一位大佬的博客给出思路

static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
    //通过指针再解释强转成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函数调用一个模板方法,然后将数据写入到一段共享的内存中
读过程

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;
    }
}

读写起始地址必须一致,这也是我们为什么要保持读写类的成员变量一致的原因
因为是采用写入动态内存的方法,所以如果要保证数据持久化的、避免因为突发状况(like 断电)产生不可预计的错误,尽量还是使用Serializable
但是因为Parcelable是对内存直接将进行读写的,且直接使用指针操作,所以效率上会快很多,也适用于专注于效率的场合,例如进程间小数据的通信

总结

前面笔者提出的疑问已经在源码解答部分给出了解释,这里就不赘述了,觉得博客还可以的朋友点个赞吧

参考文章
Android 序列化(Serializable和Parcelable)
Android Parcelable原理分析
Serializable 和Parcelable 的区别(Android每日面试题)
Java对象序列化底层原理源码解析WhatHowWhyOther

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值