Intent(意圖)和序列化和反序列化

 

Serializable方式

Serializable是Java 提供的一個序列化接口,它是一個空接口,爲對象提供標準的序列化和反序列化操作,表示將一個對象轉換成可存儲或可傳輸的狀態。序列化後的對象可以在網絡上進行傳輸,也可以存儲到本地。至於序列化的方法也很簡單,只需要讓一個類去實現Serializable這個接口就可以了。

比如說有一個Person類,其中包含了name和age這兩個字段,想要將它序列化就可以這樣寫:

 

public class Person implements Serializable {
    privateString name;
    privateint age;

    publicString getName() {
        return name;
    }

    publicvoid setName(String name) {
        this.name = name;
    }

    publicint getAge() {
        return age;
    }

    publicvoid setAge(int age) {
        this.age = age;
    }
}

 

 

這裏讓Person類去實現了Serializable接口,這樣所有的Person對象就都是可序列化的了。接下來在FirstActivity中的寫法非常簡單:


 
  1. Person person =newPerson();
  2. person.setName("Tom");
  3. person.setAge(20);
  4. Intent intent =newIntent(FirstActivity.this,SecondActivity.class);
  5. intent.putExtra("person_data", person);
  6. startActivity(intent);

可以看到,這裏我們創建了一個Person的實例,然後就直接將它傳入到putExtra()方法中了。由於Person類實現了Serializable接口,所以纔可以這樣寫。

接下來在SecondActivity中獲取這個對象也很簡單,寫法如下:


 
  1. Person person =(Person) getIntent().getSerializableExtra("person_data");

這裏調用了getSerializableExtra()方法來獲取通過參數傳遞過來的序列化對象,接着再將它向下轉型成Person對象,這樣我們就成功實現了使用Intent來傳遞對象的功能了。

如果你想要將對象序列化存儲到本地文件中,除了要實現 Serializable 接口外,還要在類的聲明中指定下面的標識,採用ObjectOutPutStream 和 ObjectInputStream即可實現

 

private static final long serialVersionUID = 8711368828010083044L;

 

 

 

public class User implements Serializable {
    private static final long serialVersionUID=1L;
    public int userId;
    public String userName;
    public boolean isMale;
    ......
}

 

 

恢復後的newUser對象雖然和user的內容完全一樣,但是兩者並不是同一個對象

 	    //序列化過程
            User user = new User();
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.text"));
            outputStream.writeObject(user);
            outputStream.close();

            //反序列過程
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cache.text"));
            User newUser = (User) inputStream.readObject();	    //序列化過程
            User user = new User();
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.text"));
            outputStream.writeObject(user);
            outputStream.close();

            //反序列過程
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cache.text"));
            User newUser = (User) inputStream.readObject();




這個serialVersionUID 是用來輔助序列化和反序列化過程的,原則上序列化後的數據中的serialVersionUID 只有和當前類的serialVersionUID 相同才能夠正常的被反序列化。它的工作機制是這樣的,序列化的時候系統會把當前類的serialVersionUID 寫入序serialVersionUID列化的文件中(也可能是其他中介),當反序列化的時候,系統會去檢測文件中的serialVersionUID ,看它是否和當前類的 serialVersionUID 一致,如果一致就說明序列化的類的版本和當前類的版本是相同的,這個時候可以成功的反序列化,否則就說明當前類和序列話的類相比發生了某些變化,比如成員變量的數量。類型可能發生了改變,這個時候是無法正常反序列話化的,會報如下錯誤:

 

java.io.InvalidClassException:Main; local class incompatible: stream classdesc serialVersionUID = 8711368828010083044,
 local class  serialVersionUID = 8711368828010083043


手動給serialVersionUID指定一個值,如 1L,可極大的避免反序列化過程的失敗,如不指定serialVersionUID的值,反序列話時,當前類有所改變,比如版本升級後我們可能增加或刪除了某個成員變量,那麼系統就會重新計算當前類的 hash值,並將它賦值給serialVersionUID,這個當前類的serialVersionUID就和序列化數據中的serialVersionUID不一致,序列化失敗。

 

 

如果類發生了結構性的變化,比如修改了類名,修改了成員變量的類型,這個時候儘管serialVersionUID驗證通過了,但是反序列化過程還是會失敗,因爲類結構發生了毀滅性的改變,根本無法從老版本的數據中還原出一個新的類結構對象

Parcelable方式

除了Serializable之外,使用Parcelable也可以實現相同的效果,不過不同於將對象進行序列化,Parcelable方式的實現原理是將一個完整的對象進行分解,而分解後的每一部分都是Intent所支持的數據類型,這樣也就使用Intent和Binder實現傳遞對象的功能了。下面我們來看一下Parcelable的實現方式,修改Person中的代碼,如下所示:

 

 

    public class Person implements Parcelable

    {
        privateString name;
        privateint age;
	……
        @Override
        publicint describeContents () {
        return 0;
    }
        @Override
        publicvoid writeToParcel (Parcel dest,int flags){
        dest.writeString(name);// 寫出name
        dest.writeInt(age);// 寫出age
    }
        public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator < Person > () {
        @Override
        publicPerson createFromParcel (Parcel source){
            Person person = newPerson();
            person.name = source.readString();// 讀取name
            person.age = source.readInt();// 讀取age
            return person;
        }
        @Override
        publicPerson[] newArray ( int size){
            return new Person[size];
        }
    } ;
    }

 

 

首先我們讓Person類去實現了Parcelable接口,這樣就必須重寫describeContents()和writeToParcel()這兩個方法。其中describeContents()方法幾乎在所有情況下都應該返回0,僅僅噹噹前對象中存在文件描述時,此方法返回1.(返回1這裏不太明白,有大神解讀下嗎?)writeToParcel()方法中我們需要調用Parcel的writeXxx()方法將Person類中的字段一一寫出。注意字符串型數據就調用writeString()方法,整型數據就調用writeInt()方法,以此類推。

  除此之外,我們還必須在Person類中提供一個名爲CREATOR的常量,這裏創建了Parcelable.Creator接口的一個實現,並將泛型指定爲Person。接着需要重寫createFromParcel()和newArray()這兩個方法,在createFromParcel()方法中我們要去讀取剛纔寫出的name和age字段,並創建一個Person對象進行返回,其中name和age都是調用Parcel的readXxx()方法讀取到的,注意這裏讀取的順序一定要和剛纔寫出的順序完全相同。而newArray()方法中的實現就簡單多了,只需要new出一個Person數組,並使用方法中傳入的size作爲數組大小就可以了。

接下來在FirstActivity中我們仍然可以使用相同的代碼來傳遞Person對象,只不過在SecondActivity中獲取對象的時候需要稍加改動,如下所示:


 
  1. Person person =(Person) getIntent().getParcelableExtra("person_data");

 

注意這裏不再是調用getSerializableExtra()方法,而是調用getParcelableExtra()方法來獲取傳遞過來的對象了,其他的地方都完全相同。

 

 

public class User implements Parcelable {

    public User(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }

    public int userId;
    public String userName;
    public boolean isMale;
    public Book book;

    protected User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() == 1;
        book=in.readParcelable(Thread.currentThread().getContextClassLoader());
    }

    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];
        }
    };

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(userId);
        parcel.writeString(userName);
        parcel.writeByte((byte) (isMale ? 1 : 0));
        parcel.writeParcelable(book,0);
    }
}


這裏要注意,在User(Parcel in)方法中,由於book是另一個可序列化對象,所以它的序列化過程需要傳遞當前線程的上下文類加載器,否則會報無法找到類的錯誤。

 

Serializable 和 Parcelable 區別

Android 中自定義的對象序列化的問題有兩個選擇一個是Parcelable,另外一個是Serializable(Java)。
一 序列化原因:
1、 永久性保存對象,保存對象的字節序列到本地文件中;
2、通過序列化對象在網絡中傳遞對象;
3、通過序列化在進程間傳遞對象。 
二 至於選取哪種可參考下面的原則:
1、 在使用內存的時候,Parcelable 類比Serializable性能高,所以推薦使用Parcelable類。
2、Serializable在序列化的時候會產生大量的臨時變量,從而引起頻繁的GC。
3、如要要將數據儲存在磁盤上,使用Serializable,因爲在外界有變化的情況下,Parcelable不能很好的保證數據的持續性,Android的Parcelable的設計初衷是因爲Serializable效率過慢,爲了在程序內不同組件間以及不同Android程序間(AIDL)高效的傳輸數據而設計,這些數據僅在內存中存在,Parcelable是通過IBinder通信的消息的載體。

實現方法:
①Serializable 的實現,只需要繼承  implements Serializable 即可。這只是給對象打了一個標記,系統會自動將其序列化。
②Parcelabel 的實現,需要在類中添加一個靜態成員變量 CREATOR,這個變量需要繼承 Parcelable.Creator 接口。

 

 

 

 

發佈了38 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章