Serializable和Parcelable的再次回憶

自己開發Android也有些時間了,SerializableParcelable遇到過不止一次了。但是每次別人問起具體的內容自己偏偏記得不是很清晰。因為某些原因再次梳理一下,以文章的形式給自己存儲下來。溫故而知新

序列化和反序列化幾乎是工程師們每天都要面對的事情,但是要精確掌握這兩個概念并不容易:一方面,它們往往作為框架的一部分出現而湮沒在框架之中;另一方面,它們會以其他更容易理解的概念出現,例如加密、持久化。然而,序列化和反序列化的選型卻是系統設計或重構一個重要的環節,在分布式、大數據量系統設計里面更為顯著。恰當的序列化協議不僅可以提高系統的通用性、強健性、安全性、優化系統性能,而且會讓系統更加易于調試、便于擴展。總而言之搞懂序列化是很重要滴。

序列化

將對象的狀態信息轉換為可以存儲或傳輸的形式的過程。在序列化期間,對象將其當前狀態寫入到臨時或持久性存儲區。

反序列化

就是讀取序列化后保存在存儲區的序列化信息或反序列化對象的狀態,重新創建該對象。

序列化和反序列化通常是并存的,他們的關系就像進行加密和解密操作一樣。前者需要相同的序列化方式,后者需要知道秘鑰。

Android中將對象序列化的方式有兩種SerializableParcelable這兩個接口都可以完成。SerializableJava自帶的序列化方法,而Android原生的序列化為Parcelable。這并不意味著在Android中可以拋棄Serializable,只能說在AndroidParcelable方法實現序列化更有優勢。下邊我們可以具體來看看這兩個接口實現。(PS:對象參加序列化的時候其類的結構是不能發生變化的,以下Demo都是在此基礎上演示)

Serializable

我們先看一個實現Serializable接口的類:

public class User implements Serializable {
    private static final long serialVersionUID = -4454266436543306544L;
    public int userId;
    public String userName;
    public User(int userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }
}

Serializablejava提供的一個序列化接口,它是一個空接口,專門為對象提供標準的序列化和反序列化操作,使用Serializable來實現序列化相當簡單,只需要在類的聲明中指定一個類似下面的標示即可自動實現默認的序列化過程(serialVersionUID可自動生成也可自己寫,如0L,1L,2L...)。

private static final long serialVersionUID = -4454266436543306544L;

通過Serializable方式來實現對象的序列化,實現起來非常簡單,幾乎素有工作都被系統自動完成。如果進行對象的序列化和反序列化也非常簡單,只需要采用ObjectOutputStreamObjectInputStream即可輕松實現。下面舉個簡單的列子:

//序列化過程
User user = new User(1, "Tom");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
//反序列化過程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newuser = (User) in.readObject();
in.close();

上述代碼采用Serializable方式序列化對象的典型過程,只需要把實現Serializable接口的User對象寫到文件中就可以快速恢復了,恢復后的對象newUserUser的內容完全一致,但是兩個并不是同一個對象。

serialVersionUID

上面提到,想讓一個對象實現序列化,只需要這個類實現Serializable接口并聲明一個serialVersionUID即可,實際上,甚至這個serialVersionUID也不是必需的,我們不聲明這個serialVersionUID同樣可以實現序列化,但是這將會對反序列化過程產生影響,具體會產生什么影響呢?

解決這個問題前我們先提一個問題,為什么需要serialVersionUID呢?

因為靜態成員變量屬于類不屬于對象,不會參與序列化過程,使用transient關鍵字標記的成員變量也不參與序列化過程。 (PS:關鍵字transient,這里簡單說明一下,Javaserialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization機制來保存它。為了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。當一個對象被序列化的時候,transient型變量的值不包括在序列化的表示中,然而非transient型的變量是被包括進去的)

這個時候又有一個疑問serialVersionUID是靜態成員變量不參與序列化過程,那么它的存在與否有什么影響呢?

具體過程是這樣的:序列化操作的時候系統會把當前類的serialVersionUID寫入到序列化文件中,當反序列化時系統會去檢測文件中的serialVersionUID,判斷它是否與當前類的serialVersionUID一致,如果一致就說明序列化類的版本與當前類版本是一樣的,可以反序列化成功,否則失敗。

InvalidClassException

接下來我們回答聲明serialVersionUID對進行序列化有啥影響?

如果不手動指定serialVersionUID的值,反序列化時當前類有所改變,比如增加或者刪除了某些成員變量,那么系統就會重新計算當前類的hash值并且把它賦值給serialVersionUID,這個時候當前類的serialVersionUID就和序列化的數據中的serialVersionUID不一致,于是反序列化失敗。所以我們手動指定serialVersionUID的值能很大程度上避免了反序列化失敗。

以上就是自己對Serializable的認識,下邊來看看Parcelable相關的知識

Parcelable

我們先看一個使用Parcelable進行序列化的例子:

public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book() {
    }

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }
    //從序列化后的對象中創建原始對象
    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        //從序列化后的對象中創建原始對象
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }
        //指定長度的原始對象數組
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
    //返回當前對象的內容描述。如果含有文件描述符,返回1,否則返回0,幾乎所有情況都返回0
    @Override
    public int describeContents() {
        return 0;
    }
    //將當前對象寫入序列化結構中,其flags標識有兩種(1|0)。
    //為1時標識當前對象需要作為返回值返回,不能立即釋放資源,幾乎所有情況下都為0.
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    @Override
    public String toString() {
        return "[bookId=" + bookId + ",bookName='" + bookName + "']";
    }
}

雖然Serializable可以將數據持久化在磁盤,但其在內存序列化上開銷比較大(PS:Serializabl在序列化的時候會產生大量的臨時變量,從而引起頻繁的GC),而內存資源屬于android系統中的稀有資源(android系統分配給每個應用的內存開銷都是有限的),為此android中提供了Parcelable接口來實現序列化操作,在使用內存的時候,ParcelableSerializable性能高,所以推薦使用Parcelable

Parcelable內部包裝了可序列化的數據,可以在Biander中自由的傳輸,從代碼中可以看出,在序列化過程中需要實現的功能有序列化,反序列化和內容描述。序列化功能是由writetoParcel方法來完成,最終是通過Parcel中的一系列write方法來完成的。反序列化功能是由CREATOR方法來完成,其內部標明了如何創建序列化對象和數組,并通過Parcel的一系列read方法來完成反序列化過程(PS:writeread的順序必須一致~!);內容描述功能是有describeContents方法來完成,幾乎所有情況下這個方法都應該返回0,僅當當前對象中存在文件描述符時,此方法返回1.

系統已經給我們提供了許多實現了Parcelable接口類,他們都是可以直接序列化的,比如IntentBundleBitmap等,同事List和Map也支持序列化,提前是他們里面的每個元素都是可以序列化的。

總結

既然ParcelableSerializable都能實現序列化并且都可以用于Intent間傳遞數據,那么二者改如果選擇呢?SerializableJava中的序列化接口,其使用起來簡單但是開銷很大,序列化和反序列化過程需要大量的I/O操作。而ParcelableAndroid中序列化方法,因為更適合于在Android平臺上,它的缺點就是使用起來比較麻煩,但是它的效率很高,這是Android推薦的序列化方法,因為我們要首選ParcelableParcelable主要用于內存序列化上,通過Parcelable將對象序列化到存儲設備中或者將對象序列化后通過網絡傳輸也都是可以的,但是這個過程稍顯復雜,因此在這兩種情況下建議使用Serializable

文章部分摘自任玉剛的《Android開發藝術探索》。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容