Serialiable和Parcelable的用法和區別
在Android中,經常會遇到這樣的情況,如何對兩個Activity之間傳遞訊息,熟悉Android開發的,肯定知道用Intent,對於自定義的Object,我們會使用Bundle
中的putSerializable()
,或者Bundle.putParcelable()
,可是我比較喜歡用Serialiable
的方式,因為簡答啊,程式碼少啊,可是在Android中程序間通訊裡,我們要傳遞一個自定義的Object,需要使用Parceable
的方式,所以得搞搞它們。
Java中的序列化
在Java 1.1 增添了一種特性,叫“物件序列化”(Object Serialization)。它面向那些實現了Serializable介面的物件,可將它們轉換成一系列位元組,並可在以後完全恢復原來的樣子。這一過程可通過網路進行。這意味著序列化機制能自動補償作業系統間的差異。
物件序列化不僅儲存了物件的“全景圖”,而且能追蹤物件內包含的所有控制代碼並儲存那些物件,接著又能對每個物件內包含的控制代碼進行追蹤,以此類推。
先看一個例子:
class Data implements Serializable{
private int i;
Data(int x){
i = x;
}
@Override
public String toString() {
return Integer.toString(i);
}
}
public class Worm implements Serializable {
private static int r(){
return (int)(Math.random()*10);
}
private Data[] d = {
new Data(r()),new Data(r()),new Data(r())
};
private Worm next;
private char c;
Worm(int i,char x){
System.out.println("Worm constructor: "+i);
c = x;
if (--i>0){
next = new Worm(i,(char)(x+1));
}
}
Worm(){
System.out.println("Default constructor");
}
@Override
public String toString() {
String s = ": "+c+"(";
for(int i = 0;i<d.length;i++) {
s += d[i].toString();
}
s+=")";
if(next!=null){
s+=next.toString();
}
return s;
}
public static void main(String[] args){
Worm worm = new Worm(6,'a');
System.out.println("worm = "+worm);
try {
ObjectOutputStream outputStream =
new ObjectOutputStream(new FileOutputStream("worm.out"));
outputStream.writeObject("Worm storage");
outputStream.writeObject(worm);
outputStream.close();
ObjectInputStream inputStream =
new ObjectInputStream(new FileInputStream("worm.out"));
String s = (String)inputStream.readObject();
Worm worm1 = (Worm)inputStream.readObject();
System.out.println(s+", w2 = "+worm1);
}catch (FileNotFoundException e){
}catch (IOException e){
}catch (ClassNotFoundException e){
}
}
}
輸出結果:
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
worm = : a(158): b(633): c(934): d(547): e(919): f(612)
Worm storage, w2 = : a(158): b(633): c(934): d(547): e(919): f(612)
我們可以看到,在對一個Serialiable物件進行重新裝配的過程中,不會呼叫任何構造器。整個物件都是從InputStream中取得資料恢復的。
序列化控制
如果我們希望物件的某一部分序列化,或者某一個子物件完全不必序列化,此時,可是通過實現Externliable
介面,用它替代Serialiable
介面,便可控制序列化的具體過程。Externliable
介面擴充套件了Serialiable
,並增添了兩個方法:writeExternal()
和readExternal()
。在序列化裝配的過程中,會自動呼叫這兩個方法,以便我們執行一些特殊操作。
class Blip1 implements Externalizable {
public Blip1() {
System.out.println("Bilp1 Constructor");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Bilp1 writeExternal");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Bilp1 readExternal");
}
}
class Bilp2 implements Externalizable{
Bilp2() {
System.out.println("Bilp2 Constructor");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Bilp2 writeExternal");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Bilp2 readExternal");
}
}
public class Blips{
public static void main(String[] args){
Blip1 b1 = new Blip1();
Bilp2 b2 = new Bilp2();
try {
ObjectOutputStream outputStream =
new ObjectOutputStream(new FileOutputStream("Blips.out"));
System.out.println("Saving objects");
outputStream.writeObject(b1);
outputStream.writeObject(b2);
outputStream.close();
ObjectInputStream inputStream =
new ObjectInputStream(new FileInputStream("Blips.out"));
System.out.println("Recovering b1");
b1 = (Blip1)inputStream.readObject();
System.out.println("Recovering b2");
b2 = (Bilp2)inputStream.readObject();
}catch (FileNotFoundException e){
}catch (IOException e){
}catch (ClassNotFoundException e){
}
}
}
結果:
Bilp1 Constructor
Bilp2 Constructor
Saving objects
Bilp1 writeExternal
Bilp2 writeExternal
Recovering b1
Bilp1 Constructor
Bilp1 readExternal
Recovering b2
上面結果,我們可以看到b2沒有被恢復,這是因為在上面程式碼中,b2的構造器不是public的。我們可以看到,在恢復b1的時候,會呼叫Blip1的構造器,這是和Serialiable
的不同。在Serialiable
中,物件完全以它儲存下來的二進位制位為基礎恢復,不存在構造器呼叫。而對Externalizable
物件,所有普通的預設構建行為都會發生(包括在欄位定義時的初始化)。
剛才我們有說,Externalizable
可是讓我們自己控制,我們看看下面一個例子:
class Blip3 implements Externalizable{
public int x;
public int y;
public String s;
public Blip3(String s) {
super();
System.out.println("Bilp3(String s)");
this.s = s;
}
public Blip3() {
System.out.println("Bilp3 Default Constructor");
}
@Override
public String toString() {
System.out.println("toString");
return "toString: "+s+"---"+x+"--"+y;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip3 writeExternal");
out.writeObject(this.x);
out.writeObject(this.s);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Blip3 readExternal");
this.x = (int)in.readObject();
this.s = (String) in.readObject();
}
}
public static void main(String[] args){
Blip3 b3 = new Blip3("333333333");
b3.x = 10;
b3.y = 11;
System.out.println(b3.toString());
try {
ObjectOutputStream outputStream =
new ObjectOutputStream(new FileOutputStream("Blip3.out"));
System.out.println("Saving objects");
//outputStream.writeObject(b1);
//outputStream.writeObject(b2);
outputStream.writeObject(b3);
outputStream.close();
ObjectInputStream inputStream =
new ObjectInputStream(new FileInputStream("Blip3.out"));
System.out.println("Recovering b3");
b3 = (Blip3) inputStream.readObject();
System.out.println(b3.toString());
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
輸出結果:
Bilp3(String s)
toString
toString: 333333333---10--11
Saving objects
Blip3 writeExternal
Recovering b3
Bilp3 Default Constructor
Blip3 readExternal
toString
toString: 333333333---10--0
我們可以看到y是沒有傳過來的,而且Externalizable
會呼叫的是預設構造器,如果沒有預設構造器,會報錯。
在使用Serializable
序列化時,我們通常會指定一個serialVersionUID
,這個serialVersionUID
是來輔助序列化和反序列過程的,原則上序列化後的資料中的serialVersionUID
只有和當前類的serialVersionUID
相同,才能夠被正常的反序列化。
serialVersionUID的工作機制:序列化的時候系統會把當前類的serialVersionUID寫入系列化檔案中(也可能是其他中介),當序列化的時候系統回去檢測檔案中的serialVersionUID,看它是否和當前類的serialVersionUID一致,如果一直就說明序列化的類的版本和當前類的版本是相同的,這個時候可以成功反序列化。否則就說明當前類和序列化的類相比發生了某些變化,比如成員變數的數量,型別可能發何時能了改變,這個時候是無法正常反序列化的。
Parcelable的使用
Parceable也是一個介面,只要實現這個介面,一個類的物件就可以實現序列化並通過Intent和Binder傳遞。
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
private User(Parcel in){
this.userId = in.readInt();
this.userName = in.readString();
this.isMale = in.readInt() == 1;
}
public static final Parcelable.Creator<User> CREATOR = new
Parcelable.Creator<User>(){
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeInt(isMale?1:0);
}
public static void putUser(Context context,Class activity){
Intent intent = new Intent(context,activity);
Bundle bundle = new Bundle();
bundle.putParcelable("user",new User(1,"vivian",false));
intent.putExtra("user",bundle);
}
}
Parcel內部包裝了可序列化的資料,在意在Binder中自由傳出。序列化使由writeToParcel
方法來完成的,最終是通過Parcel中的一系列wirte方法來完成的。反序列功能右CREATOR來完成,其內部標明瞭如何建立序列化物件和陣列,並通過一系列read方法來完成反序列化的過程。
內容描述功能又describeContents方法來完成,幾乎所有情況下這個方法都應該返回0,僅噹噹前物件中存在檔案描述符時,此方法返回1.
Serializable和Parcable的區別
可是肯定的是,兩者都支援序列化和反序列的操作。
兩者最大的區別在於儲存媒介不同,Serializable
使用I/O讀寫儲存在硬碟上,而Parcable
是直接在記憶體中讀寫。很明顯,記憶體的讀寫速度通常大於IO讀寫,所以在Android中傳遞資料優先選擇Parcelable
。
Serializable
會使用反射,序列化和反序列過程需要大量I/O操作,Parcelable
自己實現封裝和解封操作不需要用反射,資料也存放在Natvie記憶體中,效率要快很多,
在兩個Activity之間傳遞物件需要注意:
物件的大小!!!
Intent 中的 Bundle 是使用 Binder 機制進行資料傳送的。能使用的 Binder 的緩衝區是有大小限制的(有些手機是 2 M),而一個程序預設有 16 個 Binder 執行緒,所以一個執行緒能佔用的緩衝區就更小了( 有人以前做過測試,大約一個執行緒可以佔用 128 KB)。所以當你看到 The Binder transaction failed because it was too large 這類 TransactionTooLargeException 異常時,你應該知道怎麼解決了。