Android序列化問題與思考

今天再來談談Android中的對象序列化,你瞭解多少呢?java

序列化指的是什麼?有什麼用

序列化指的是講對象變成有序的字節流,變成字節流以後才能進行傳輸存儲等一系列操做。
反序列化就是序列化的相反操做,也就是把序列化生成的字節流轉爲咱們內存的對象。android

介紹下Android中兩種序列化接口

Serializable

Java提供的一個序列化接口,是一個空接口,專門爲對象提供序列化和反序列化操做。具體使用以下:數組

public class User implements Serializable {
    private static final long serialVersionUID=519067123721561165l;
    
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

實現Serializable接口,聲明一個serialVersionUID緩存

到這裏可能有人就問了,不對啊,平時沒有這個serialVersionUID啊。沒錯,serialVersionUID不是必須的,由於不寫的話,系統會自動生成這個變量。它有什麼用呢?當序列化的時候,系統會把當前類的serialVersionUID寫入序列化的文件中,當反序列化的時候會去檢測這個serialVersionUID,看他是否和當前類的serialVersionUID一致,同樣則能夠正常反序列化,若是不同就會報錯了。網絡

因此這個serialVersionUID就是序列化和反序列化過程當中的一個標識,表明一致性。不加的話會有什麼影響?若是咱們序列化後,改動了這個類的某些成員變量,那麼serialVersionUID就會改變,這時候再拿以前序列化的數據來反序列化就會報錯。因此若是咱們手動指定serialVersionUID就能保證最大限度來恢復數據。ide

Serializable的實質實際上是是把Java對象序列化爲二進制文件,而後就能在進程之間傳遞,而且用於網絡傳輸或者本地存儲等一系列操做,由於他的本質就存儲了文件。能夠看看源碼:函數

private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    ...
    try {
     
        Object orig = obj;
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc;
       
        desc = ObjectStreamClass.lookup(cl, true);
   
        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
    {
        ...
        try {
            desc.checkSerialize();
            
            //寫入二進制文件,普通對象開頭的魔數0x73
            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();
            }
        }
    }

    public long getSerialVersionUID() {
        // 若是沒有定義serialVersionUID,序列化機制就會調用一個函數根據類內部的屬性等計算出一個hash值
        if (suid == null) {
            suid = AccessController.doPrivileged(
                new PrivilegedAction<Long>() {
                    public Long run() {
                        return computeDefaultSUID(cl);
                    }
                }
            );
        }
        return suid.longValue();
    }

能夠看到是經過反射獲取對象以及對象屬性的相關信息,而後將數據寫到了一個二進制文件,而且寫入了序列化協議版本等等。
而獲取·serialVersionUID·的邏輯也體現出來,若是id爲空則會生成計算一個hash值。post

Parcelable

Android自帶的接口,使用起來要麻煩不少:須要實現Parcelable接口,重寫describeContents(),writeToParcel(Parcel dest, @WriteFlags int flags),並添加一個靜態成員變量CREATOR並實現Parcelable.Creator接口學習

public class User implements Parcelable {
    
    private int id;

    protected User(Parcel in) {
        id = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
    }

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

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}
  • createFromParcel,User(Parcel in) ,表明從序列化的對象中建立原始對象
  • newArray,表明建立指定長度的原始對象數組
  • writeToParcel,表明將當前對象寫入到序列化結構中。
  • describeContents,表明返回當前對象的內容描述。若是還有文件描述符,返回1,不然返回0。

Parcelable的存儲是經過Parcel存儲到內存的,簡單地說,Parcel提供了一套機制,能夠將序列化以後的數據寫入到一個共享內存中,其餘進程經過Parcel能夠從這塊共享內存中讀出字節流,並反序列化成對象。ui

這其中實際又是經過native方法實現的。具體邏輯我就沒有去分析了,若是有大神朋友能夠在評論區解析下。

固然,Parcelable也是能夠持久化的,涉及到Parcel中的unmarshallmarshall方法。 這裏簡單貼一下代碼:

protected void saveParce() {
        FileOutputStream fos;
        try {
            fos = getApplicationContext().openFileOutput(TAG,
                    Context.MODE_PRIVATE);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            Parcel parcel = Parcel.obtain();
            parcel.writeParcelable(new ParceData(), 0);

            bos.write(parcel.marshall());
            bos.flush();
            bos.close();
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected void loadParce() {
        FileInputStream fis;
        try {
            fis = getApplicationContext().openFileInput(TAG);
            byte[] bytes = new byte[fis.available()];
            fis.read(bytes);
            Parcel parcel = Parcel.obtain();
            parcel.unmarshall(bytes, 0, bytes.length);
            parcel.setDataPosition(0);

            ParceData data = parcel.readParcelable(ParceData.class.getClassLoader());
            fis.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

總結

0)二者的區別,咱們該怎麼選擇?

Serializable是Java提供的序列化接口,使用簡單可是開銷很大,序列化和反序列化過程都須要大量I/O操做。
Parcelable是Android中提供的,也是Android中推薦的序列化方式。雖然使用麻煩,可是效率很高。

因此,若是是內存序列化層面,那麼仍是建議Parcelable,由於他效率會比較高。
若是是網絡傳輸和存儲磁盤狀況,就推薦Serializable,由於序列化方式比較簡單,並且Parcelable不能保證,當外部條件發生變化時數據的連續性。

1)對於內存序列化方面建議用Parcelable,爲何呢?

  • 由於Serializable是存儲了一個二進制文件,因此會有頻繁的IO操做,消耗也比較大,並且用到了大量反射,反射操做也是耗時的。相比之下Parcelable就要效率高不少。

2)對於數據持久化仍是建議用Serializable,爲何呢?

  • 首先,Serializable自己就是存儲到二進制文件,因此用於持久化比較方便。而Parcelable序列化是在內存中操做,若是進程關閉或者重啓的時候,內存中的數據就會消失,那麼Parcelable序列化用來持久化就有可能會失敗,也就是數據不會連續完整。並且Parcelable還有一個問題是兼容性,每一個Android版本可能內部實現都不同,知識用於內存中也就是傳遞數據的話是不影響的,可是若是持久化可能就會有問題了,低版本的數據拿到高版本可能會出現兼容性問題。 因此仍是建議用Serializable進行持久化。

3)Parcelable必定比Serializable快嗎?

  • 有個比較有趣的例子是:當序列化一個超級大的對象圖表(表示經過一個對象,擁有經過某路徑能訪問到其餘不少的對象),而且每一個對象有10個以上屬性時,而且Serializable實現了writeObject()以及readObject(),在平均每檯安卓設備上,Serializable序列化速度大於Parcelable 3.6倍,反序列化速度大於1.6倍.

具體緣由就是由於Serilazable的實現方式中,是有緩存的概念的,當一個對象被解析事後,將會緩存在HandleTable中,當下一次解析到同一種類型的對象後,即可以向二進制流中,寫入對應的緩存索引便可。可是對於Parcel來講,沒有這種概念,每一次的序列化都是獨立的,每個對象,都看成一種新的對象以及新的類型的方式來處理。

參考

Parcelable

拜拜

有一塊兒學習的小夥伴能夠關注下❤️個人公衆號——碼上積木,天天剖析一個知識點,咱們一塊兒積累知識。

相關文章
相關標籤/搜索