Serializable & Parcelable

對象序列化的簡單介紹

所謂對象的序列化其實就是把JVM運(yùn)行過程中生成的對象通過特殊的處理手段轉(zhuǎn)換為字節(jié)形式的文件。轉(zhuǎn)換之后就可以將其永久保存到磁盤中程储,或者以字節(jié)流進(jìn)行網(wǎng)絡(luò)傳輸。

在Android中使用Intent傳遞數(shù)據(jù)時,基本數(shù)據(jù)類型可以直接傳遞,而比較復(fù)雜的引用類型的數(shù)據(jù)就需要先將對象序列化再進(jìn)行傳遞梆惯。

序列化的轉(zhuǎn)換只是將對象的屬性進(jìn)行序列化,不針對方法進(jìn)行序列化吗垮。

Android中有兩種實現(xiàn)序列化的方法垛吗,一種是實現(xiàn)Java提供的Serializable接口,另一種是Android提供的Parcelable接口烁登。

使用Serializable 序列化對象

Serializable是Java提供的接口(interface)怯屉,它里面沒有任何的屬性跟方法,純粹就是起到個標(biāo)識的作用饵沧。如果想讓某個類下的對象能夠序列化需要先實現(xiàn)Serializable接口锨络。

例如,我們想讓一個Person類的對象能夠序列化狼牺,這個類就需要被聲明為:

public class Person implements Serializable{

    private String name;
    private int age;
    
    ...
}

之后我們就可以將Person類的對象序列化寫入文件中永久保存了羡儿,這個環(huán)節(jié)你需要ObjectOutStream的幫助:

// 構(gòu)造一個指定具體文件的ObjectOutStream ,path為文件的路徑
ObjectOutputStream out = new ObjectOutputStream(Files.newOutPutStream(path));

//實例化對象
Person peter = new Person("peter" , 18);
Person mike = new Person("mike" , 20);

// 寫入對象
out.writeObject(peter);
out.writeObject(mike);

上面代碼就完成了寫入對象的操作是钥,要想讀回對象的話需要用到ObjectInputStream

ObjectInputStream in = new ObjectInputStream(Files.newInPutStream(path));

// 讀取 peter
Person p1 = (Person) in.readObject(); 

// 讀取mike
Person p2 = (Person) in.readObject(); 

注意掠归!讀取對象的順序與寫入對象的順序是一致的缅叠。

如果序列化對象的屬性是基本數(shù)據(jù)類型的則會以二進(jìn)制形式保存數(shù)據(jù),如果屬性也是一個對象那么它會被writeObject()再次寫入虏冻,直到所有屬性都是基本數(shù)據(jù)類型為止痪署。

還有一點(diǎn),如果寫入的兩個對象里引用了同一個對象兄旬,當(dāng)讀取回這兩個對象時它們引用的對象還是同一個,而不會是兩個內(nèi)容相同卻是不同引用的對象余寥。這歸功于在讀寫對象時會為每個對象記錄一個唯一序列號领铐。

使用transient關(guān)鍵字忽略某些屬性

在實際中某些屬性是不需要被序列化的,例如數(shù)據(jù)庫連接對象就沒必要序列化宋舷,為了實現(xiàn)某些屬性不被序列化绪撵,我們可以給這些屬性加上一個transient修飾標(biāo)記符,那么這些屬性在序列化時就會被自動忽略祝蝠。

public class Person implements Serializable{

    private String name;
    private int age;
    
    // 不需要序列化的屬性
    private transient Connection mConn;
    ...
}

關(guān)于序列化版本

有時候我們會將序列化的對象從一臺JVM傳到另一臺JVM上運(yùn)行音诈,為保證讀取的對象與寫入的對象一致,JVM在寫入對象的時候為類分配了一個serialVersionUID屬性.

serialVersionUID屬性用來標(biāo)識當(dāng)前序列化對象的類版本绎狭,如果我們沒有手動指定它细溅,JVM會根據(jù)類的信息自動生成一個UID。但如果是兩臺JVM互傳數(shù)據(jù)時為保證類的一致性儡嘶,我們最好自己手動聲明這個屬性:

public class Person implements Serializable{

    // 序列化的版本喇聊,自己定義具體數(shù)據(jù)來實現(xiàn)每次的版本更新
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    
    // 不需要序列化的屬性
    private transient Connection mConn;
    ...
}

自定義序列化細(xì)節(jié)

到現(xiàn)在為止我們序列化對象的方法只是直接調(diào)用了Java的API,序列化的過程全部由Java幫我們默認(rèn)實現(xiàn)蹦狂。但是有些情況我們需要在序列化時進(jìn)行一些特殊處理誓篱,例如某些表示狀態(tài)的屬性序列化時不需要保存而反序列化成對象時希望能夠被賦值,顯然transient關(guān)鍵字不能幫我們實現(xiàn)凯楔,這時候我們就需要自定義序列化的細(xì)節(jié)窜骄。

ObjectOutputStreamObjectInputStream在序列化與反序列化時會檢查我們的類是否聲明了如下幾個方法:

  • void writeObject(ObjectOutputStream oos) throws IOException
    序列化對象時調(diào)用的方法
  • void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
    反序列化對象時調(diào)用的方法
  • Object writeReplace() throws ObjectStreamException ObjectOutPutStream
    序列化對象之前調(diào)用的方法,在這里可以替換真正被序列化的對象
  • Object readResolve() throws ObjectStreamException
    在反序列化對象后調(diào)用的方法摆屯,在這里可以替換反序列化后得到的對象

以上方法如果你自己聲明了那么就執(zhí)行你自定義的方法邻遏,否則使用系統(tǒng)默認(rèn)的方法。至于自定義方法的權(quán)限修飾符private protected public都無所謂鸥拧,因為使用ObjectXXXputStream使用反射調(diào)用的党远。他們在序列化與反序列化的調(diào)用流程如下圖。

序列化與反序列化流程

此四個方法你可以根據(jù)需要任意替換成自己的方法富弦,不過一般都是都是讀寫成對替換的沟娱,下面看我們?nèi)绾斡米远x方法實現(xiàn)序列化:

public class Person implements Serializable{

    // 序列化的版本,自己定義具體數(shù)據(jù)來實現(xiàn)每次的版本更新
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    
    // 不需要序列化的屬性
    private transient Connection mConn;

    public Person(String name,int age){
        this.name = name;
        this.age = age;  
    } 

    private void writeObject(ObjectOutputStream oos)  throws IOException {
        // 默認(rèn)的序列化對象方法
        out.defaultWriteObject();
        //我們自定義添加的東西
        out.writeInt(100);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
          // 默認(rèn)反序列化方法
         in.defaultReadObject();
         // 讀出我們自定義添加的東西
         int flag = in.readInt();
         System.out.println(flag);
    }

    private Object writeReplace(){
         // 替換真正序列化的對象
         return new Person(name,age);
    }

    private Object readResolve(){
        // 替換反序列化后的對象
        return new Person(name,age);
    }
}

此處你可能對writeReplace()readResolve()方法的用處有疑問腕柜,在下面序列化代理中會見識到它們的用處济似。

關(guān)于反序列化需要注意的

從字節(jié)流中讀取的數(shù)據(jù)后反序列化的對象并不是通過構(gòu)造器創(chuàng)建的矫废,那么很多依賴于構(gòu)造器保證的約束條件在對象反序列化時都無法保證。比如一個設(shè)計成單例的類如果能夠被序列化就可以分分鐘克隆出多個實例...

序列化代理

在知道了Java在反序列化時并不是通過構(gòu)造器創(chuàng)建的對象砰蠢,那么別人只需要解析你序列化后的字節(jié)碼就能夠輕而易舉的獲取你的內(nèi)容蓖扑,不僅如此,再利用同樣的序列化格式生成任意的字節(jié)碼送你你的程序分分鐘就攻破你的程序台舱。

為解決該隱患律杠,大神們推薦我們使用靜態(tài)內(nèi)部類作為代理來進(jìn)行類的序列化:

public class Person implements Serializable{

    // 序列化的版本,自己定義具體數(shù)據(jù)來實現(xiàn)每次的版本更新
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    
    // 不需要序列化的屬性
    private transient Connection mConn;

    public Person(String name,int age){
        this.name = name;
        this.age = age;  
    } 

    // 把真正要序列化的對象替換成PersonProxy 代理
    private Object writeReplace() {
        return new PersonProxy (this);
    }

    // 因為真正被序列化的對象是PersonProxy 代理對象竞惋,所以Person的readObject()方法永遠(yuǎn)不會執(zhí)行
    // 執(zhí)行的是PersonProxy 代理對象的readObject()方法
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
        // 如果該方法被執(zhí)行說明有流氓入侵柜去,直接拋異常
        throw new InvalidObjectException("proxy requied");
    }

    static class PersonProxy implements Serializable{
        private String name;
        private int age;

        public PersonProxy(Person person){
            this.name = person.name;
            this.age = person.age;
        }

        // 把讀取出來的代理對象再替換回Person對象
        private Object readResolve(){
            return new Person(name,age);
        }
    }
}

使用Parcelable 序列化對象

Parcelable 雖然也是序列化對象的方法,但是它跟java提供的Serializable 在使用上有著極大的差別拆宛。

Android設(shè)計 Parcelable 的目的是讓其支持進(jìn)程間通信的功能嗓奢,因此它不具備類似Serializable的版本功能,所以Parcelable 不適合永久存儲浑厚。

實現(xiàn)Parcelable 接口需要滿足兩個條件:

  1. 實現(xiàn)Parcelable 接口下的兩個方法describeContents()writeToParcel(Parcel out股耽,int flags)

  2. 聲明一個非空的靜態(tài)屬性CREATOR且類型為Parcelable.Creator <T>钳幅。

例如我們想讓person類實現(xiàn)Parcelable 接口:

public class Person implements Parcelable {

     private int age;

     // 定義當(dāng)前傳送的 Parcelable實例包含的特殊對象的類別
     public int describeContents() {
         // 一般情況我們用不到物蝙,直接為0就行 
         return 0;
     }

     // 在該方法中將對象的屬性寫入字節(jié)流
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(age);
     }

     // 該靜態(tài)屬性會從Parcel 字節(jié)流中生成Parcelable類的實例
     public static final Parcelable.Creator<MyParcelable> CREATOR
             = new Parcelable.Creator<Person>() {
         
         // 該方法接收Parcel解析成對應(yīng)的實例
         public Person createFromParcel(Parcel in) {
             return new Person(in);
         }
        
         // 根據(jù)size創(chuàng)建對應(yīng)數(shù)量的實例數(shù)組
         public Person[] newArray(int size) {
             return new Person[size];
         }
     };
     
     private Person(Parcel in) {
         age= in.readInt();
     }
 }

到此Person就具備了序列化的條件。至于讀和寫就看具體的需求了敢艰,最簡單的使用方法可以利用Intent傳遞茬末。

讓我驚訝的是Parcelable 的使用并沒有那么簡單,它牽扯出了一大堆進(jìn)程間通信相關(guān)的問題盖矫,待學(xué)習(xí)到進(jìn)程間通信時需要再重新梳理一遍丽惭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辈双,隨后出現(xiàn)的幾起案子责掏,更是在濱河造成了極大的恐慌,老刑警劉巖湃望,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件换衬,死亡現(xiàn)場離奇詭異,居然都是意外死亡证芭,警方通過查閱死者的電腦和手機(jī)瞳浦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來废士,“玉大人叫潦,你說我怎么就攤上這事」傧酰” “怎么了矗蕊?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵短蜕,是天一觀的道長。 經(jīng)常有香客問我傻咖,道長朋魔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任卿操,我火速辦了婚禮警检,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘害淤。我一直安慰自己解滓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布筝家。 她就那樣靜靜地躺著,像睡著了一般邻辉。 火紅的嫁衣襯著肌膚如雪溪王。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天值骇,我揣著相機(jī)與錄音莹菱,去河邊找鬼。 笑死吱瘩,一個胖子當(dāng)著我的面吹牛道伟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播使碾,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蜜徽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了票摇?” 一聲冷哼從身側(cè)響起拘鞋,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎矢门,沒想到半個月后盆色,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祟剔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年隔躲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片物延。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡宣旱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叛薯,到底是詐尸還是另有隱情响鹃,我是刑警寧澤驾霜,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站买置,受9級特大地震影響粪糙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忿项,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一蓉冈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轩触,春花似錦寞酿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至榨为,卻和暖如春惨好,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背随闺。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工日川, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矩乐。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓龄句,卻偏偏與公主長得像,于是被迫代替她去往敵國和親散罕。 傳聞我的和親對象是個殘疾皇子分歇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容