1. 程式人生 > >Serialiable和Parcelable的用法和區別

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 異常時,你應該知道怎麼解決了。

參考:https://juejin.im/post/5a24fd8151882531ea651c37