首先澄清上篇文章中一個概念,上篇文章所述的Parcelable
比Serializable
對比,前提是將Parcel機制忽略掉的,我們可以將Parcel機制看成是一種輔助手段。假如Serializable
的底層也有這麼一個高效的輔助工具的話,我們是不是就只需要考慮Parcelable
的writeToParcel()
、createFromParcel()
和對Serializabl
e的writeObject()
、readObject()
的操作對比了呢。而我們在實現Parcelable
的時候,是必須要手動去實現這兩個方法的。Serializable
就不需要,但是代價就是效率要低一些。我所說的是這一個層面的比較。當然,只是這個層面的比較是非常片面的,那我們繼續往底層看看。
看看Serializable底層序列化的原理
我們來看一眼ObjectOutputStream
中的writeObject()
方法
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
try {
writeFatalException(ex);
} catch (IOException ex2) {
}
exceptions during writeObject().
}
throw ex;
}
}
複製程式碼
如果沒有自己定義writeObject()
的方法,將會呼叫writeObject0()
方法,我們繼續往裡面看看。
private void writeObject0(Object obj, boolean unshared) throws IOException {
// ...省略程式碼
// remaining cases
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else 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) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
}
複製程式碼
在這個方法中,具體邏輯就是使用反射去構造類的物件,對類的型別判斷然後進行相應處理。
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 {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
複製程式碼
看到這裡大概也就知道了Serializable
的底層原理就是通過通過操作IO流來將物件進行寫和讀的處理。
當然,有朋友就指出了Parcelable
比Serializable
更高效的原因應該是底層的Parcel機制。講的非常對,Android中的Parcel機制就是對應的Serializable底層的操作IO流的操作。那麼為什麼有了Parcel機制,Parcelable就比Serializable能高效十來倍呢?
那麼這篇文章,我們一起來探討一下Android中的Parcel機制。
還是從我們實現Parcelable說起。
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.userId);
dest.writeString(this.name);
dest.writeInt(this.age);
}
protected UserInfo(Parcel in) {
this.userId = in.readString();
this.name = in.readString();
this.age = in.readInt();
}
@Override
public UserInfo createFromParcel(Parcel source) {
return new UserInfo(source);
}
複製程式碼
這是我們實現Parcelable程式碼中的片段,我們可以看到我們自己手動去實現的writeToParcel()
和createFromParcel()
都是交由Parcel去處理讀和寫了。
看看Parcel的讀和寫的原理
我們就拎出一個writeInt()
方法進去看看,可以看到實際上是呼叫了jni中的nativeWriteInt
方法。那麼我們就去看看在jni中的 android_os_Parcel
對應的實際呼叫應該是:
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
const status_t err = parcel->writeInt32(val);
if (err != NO_ERROR) {
signalExceptionForError(env, clazz, err);
}
}
複製程式碼
呼叫的是Parcel.cpp中的writeInt32()方法
status_t Parcel::writeInt32(int32_t val)
{
return writeAligned(val);
}
複製程式碼
在Parcel.cpp內部呼叫writeAligned()
方法
template<class T>
status_t Parcel::writeAligned(T val) {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
*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;
}
複製程式碼
這裡就是將我們需要儲存的資料寫到記憶體中的具體實現。其他的writeXXX()方法也跟此類似。
對應的反序列化操作
readInt()
方法流程跟writeInt()
型別,最終呼叫的就是Parcel.cpp中的readAligned()
方法
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 += sizeof(T);
*pArg = *reinterpret_cast<const T*>(data);
return NO_ERROR;
} else {
return NOT_ENOUGH_DATA;
}
}
複製程式碼
這裡也就是在反序列化的時候,獲取到之前儲存的資料,將資料一一還原。
Parcel機制的實現邏輯
Parcel.cpp
大概的實現邏輯就是在初始化的時候,開闢了一塊記憶體空間,然後我們在序列化物件的時候,控制position的位置把資料通過Parcel的writeXXX()方法一段一段的寫入到這塊記憶體中,整個過程是連續的。所以在我們反序列化的時候也是控制position一段一段取出記憶體中資料,在通過Parcel的readXXX()方法還原成物件中的資料。這兩個過程writeXXX()和readXXX()的順序必須是一樣的,這樣才能保證序列化和反序列化的成功。
總結兩者效率差別的真正原因
看到這裡,我們心中就有個大概的概念了,Parcel機制實際上就是通過共享記憶體的方法,實現序列化和反序列化。而Serializable是通過操作IO流來讀寫物件。所以來說,這才是真正的為什麼說Parcelable比Serializable高效十來倍的原因。
如果說Serializable通過自己去實現writeObject()和readObject(),也使用這種記憶體共享的手段,效率是不會比Parcelable差的。但如果這樣做,我們就失去了Serializable的穩定性,因為我們在此處通過自定義的方式來序列化,別的地方是無法知道我們這個序列化過程,也就沒有辦法反序列化回來了。