Android重修課 -- Parcel機制

Leon_發表於2019-01-09

首先澄清上篇文章中一個概念,上篇文章所述的ParcelableSerializable對比,前提是將Parcel機制忽略掉的,我們可以將Parcel機制看成是一種輔助手段。假如Serializable的底層也有這麼一個高效的輔助工具的話,我們是不是就只需要考慮ParcelablewriteToParcel()createFromParcel()和對Serializable的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流來將物件進行寫和讀的處理。

當然,有朋友就指出了ParcelableSerializable更高效的原因應該是底層的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的穩定性,因為我們在此處通過自定義的方式來序列化,別的地方是無法知道我們這個序列化過程,也就沒有辦法反序列化回來了。

相關文章