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中的寫法非常簡單:
Person person =newPerson();
person.setName("Tom");
person.setAge(20);
Intent intent =newIntent(FirstActivity.this,SecondActivity.class);
intent.putExtra("person_data", person);
startActivity(intent);
可以看到,這裏我們創建了一個Person的實例,然後就直接將它傳入到putExtra()方法中了。由於Person類實現了Serializable接口,所以纔可以這樣寫。
接下來在SecondActivity中獲取這個對象也很簡單,寫法如下:
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中獲取對象的時候需要稍加改動,如下所示:
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 接口。