對象的序列化存儲:Serializable 和 Parceable
在進(jìn)行Android開發(fā)的時候我們有時候需要用到數(shù)據(jù)的持久化存儲,或者在進(jìn)程之間傳遞數(shù)據(jù)。其中就可能需要用到對象的序列化,經(jīng)過序列化的對象之后可以通過Intent或者Boundle來傳輸了。接下來還是想些介紹下吧。
1.什么叫序列化,什么叫反序列化
序列化: 將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成二進(jìn)制串的過程。反序列化:將在序列化過程中所生成的二進(jìn)制串轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或者對象的過程。
簡單來說,序列化就是將我們生成的對象進(jìn)行存儲起來(比如磁盤上),以用來將來使用或者在網(wǎng)絡(luò)上進(jìn)行傳輸,而反序列化呢,就是由我們的之前序列化生成的二進(jìn)制串重新生成對象的過程。注意,這里我們反復(fù)說的序列化啦,反序列化啦,都是針對的對象,而非類。因為我們是針對對象進(jìn)行存取與傳輸?shù)?,而非類,?dāng)我們需要重新獲取之前的對象的時候,是直接讀取出來的(從文件或網(wǎng)絡(luò)中),而非根據(jù)類new出一個對象,這點是需要注意的。
2.如何序列化
序列話的方式有兩種,一種是實現(xiàn)Serializable接口,一種是實現(xiàn)Parceable接口,下面會具體介紹這兩種方式。
a.實現(xiàn)Serializable接口
這種序列化方式是Java提供的,它的優(yōu)點是簡單,其實Serializable接口是個空接口,因而我們并不需要實現(xiàn)什么抽象方法,但是我們卻往往需要在類中聲明一個靜態(tài)變量標(biāo)識(serialVersionUID),但這不是必須的,我們不聲明,依然可以實現(xiàn)序列化,但是這樣的話會對反序列化產(chǎn)生一定的影響,可能會在我們對類做了修改之后而造成對象的反序列化失敗。聲明方式如下:
- private static final long serialVersionUID = 8711368828010083044L;
注意,這里的值可以是任意值。
下面我們來具體實現(xiàn)下。
- package com.qc.admin.myserializableparceabledemo;
- import java.io.Serializable;
- /**
- * Created by admin on 2016/12/1.
- */
- public class User implements Serializable {
- private static final long serialVersionUID = 519067123721295773L;
- 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;
- }
- @Override
- public String toString() {
- return "User{ " +
- "userId = " + userId +
- ", userName = " + userName +
- ", isMale = " + isMale +
- " }";
- }
- }
下面是序列化與反序列化過程:
- private void beginSerizable() throws IOException, ClassNotFoundException {
- // 序列化
- User user = new User(2016, "qian", true);
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File(getFilesDir(), "myfile.txt")));
- out.writeObject(user);
- out.close();
- // 反序列化
- // 注意,這里后面的“/myfile.txt”前面有個斜杠“/”,否則會報“FileNotFoundException”異常
- ObjectInputStream in = new ObjectInputStream(new FileInputStream(getFilesDir() + "/myfile.txt"));
- User mUser = (User) in.readObject();
- textView.setText(mUser.toString());
- in.close();
- Log.i("test",mUser.toString());
- }
運行結(jié)果截圖:
注意:如果是在Android項目中調(diào)用以上方法,別忘了在Manifest.xml文件中配置如下權(quán)限:
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
b.實現(xiàn)Parceable接口
這種方式是Android提供的方式,相比較前面那種方式來講,這種方式稍微有點復(fù)雜,我們需要自己盡享序列化與反序列化的操作,但是它卻更加高效,并不需要執(zhí)行大量的I/O操作。而且這種方式也是Android推薦的序列化方式,因此我們應(yīng)該***Parceable。只要實現(xiàn)了這個接口,一個類的對象就可以實現(xiàn)序列化并可以通過Intent和Binder進(jìn)行傳遞了。下面請看實例:
- public class Book implements Parcelable {
- public String bookTitle;
- public int bookId;
- protected Book(Parcel in) {
- bookTitle = in.readString();
- bookId = in.readInt();
- }
- public static final Creator<Book> CREATOR = new Creator<Book>() {
- @Override
- public Book createFromParcel(Parcel in) {
- return new Book(in);
- }
- @Override
- public Book[] newArray(int size) {
- return new Book[size];
- }
- };
- @Override
- public int describeContents() {
- return 0;
- }
- @Override
- public void writeToParcel(Parcel parcel, int i) {
- parcel.writeString(bookTitle);
- parcel.writeInt(bookId);
- }
- }
這里將Book這個類就實現(xiàn)了Parcelable接口,其實在Android Studio IDE中,上述過程很簡單,我們只需要定義一個類,實現(xiàn)Parcelable接口,然后在里面定義我們的屬性或者說是字段,根據(jù)提示的錯誤,按照它提示的方法覆蓋相應(yīng)的方法,之后的一切其實都可以自動生成(不過如果需要構(gòu)造方法的話,那就需要自動生成了,toString()方法也是自己實現(xiàn)的),所以不用擔(dān)心在Android開發(fā)中通過實現(xiàn)Parceable接口會比較麻煩,因為AS都會為你自動生成。上面我們已經(jīng)完整的將Book類實現(xiàn)了Parceable接口,那接下來如何序列化和反序列化呢?如果你說,剛才不是已經(jīng)說過了嗎,采用文件讀取的方式不久可以了啦...當(dāng)你那樣做的時候,你會發(fā)現(xiàn)會報如下的錯誤:
Why???...什么情況?提示我們Book類沒有實現(xiàn)序列化:
- /System.err: java.io.NotSerializableException: com.qc.admin.myserializableparceabledemo.Book
好啦,之所以出現(xiàn)這種問題,并不是我們的實現(xiàn)過程有問題,而是使用該類的方式行不通。到這里我們就明白了Serializable和Parceable兩種方式實現(xiàn)序列化還是有區(qū)別的,剛才我們也講了,Parceable更加高效,不會像Serializable那樣有大量的I/O操作,這句話的具體含義就道出了Serializable與Parcelable區(qū)別:雖然兩者都是用于支持序列化、反序列化話操作,但是兩者***的區(qū)別在于存儲媒介的不同,Serializable是將序列化后的對象存儲在硬盤上,使用I/O讀寫的方式,而Parcelable是將其存儲在內(nèi)存中,是針對內(nèi)存的讀寫,熟悉計算機組成原理的朋友都知道,內(nèi)存的讀寫速度顯然要遠(yuǎn)遠(yuǎn)大于I/O的讀寫速度,這也是為什么Android中推薦使用Parcelable這種方式來實現(xiàn)對象的序列化。
那我們應(yīng)該怎么使用通過實現(xiàn)Parcelable接口實現(xiàn)序列化的對象呢?答案是:通過Intent方式傳遞,除了基本類型外,Intent只能傳輸序列化之后的對象,對應(yīng)這兩種序列化方式,也有兩種相應(yīng)的方法:
- mIntent.getSerializableExtra(string name );
- mIntent.getParcelableExtra(String name );
當(dāng)然,放入的操作就沒有這種區(qū)分了,都是方法:
- mIntent.putExtra();
我們可以在***個Activity中將序列化對象放入Intent,在另一個Activity中取出,比如:在另一端獲取對象,例如:
- Bundle mBundle = getIntent().getExtras();
- Book mBook = mBundle.getParcelable("book1");
下面再看類User實現(xiàn)Parceable接口的過程,它內(nèi)部包含了一個可序列化的類Book,具體細(xì)節(jié)跟上面的有點不同:
- package com.qc.admin.myserializableparceabledemo;
- import android.os.Parcel;
- import android.os.Parcelable;
- /**
- * Created by admin on 2016/12/1.
- */
- public class User implements Parcelable {
- public int userId;
- public String userName;
- public boolean isMale;
- public Book book;
- public User(int userId, String userName, boolean isMale, Book book) {
- this.userId = userId;
- this.userName = userName;
- this.isMale = isMale;
- this.book = book;
- }
- protected User(Parcel in) {
- userId = in.readInt();
- userName = in.readString();
- isMale = in.readByte() != 0;
- // 此為不同之處1
- // 也可以通過這種方式:book = in.readParcelable(Thread.currentThread().getContextClassLoader());
- book = in.readParcelable(Book.class.getClassLoader());
- }
- 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];
- }
- };
- // 幾乎在所有的情況下都應(yīng)該返回0,只有在當(dāng)前對象中存在文件描述的時候,此方法返回CONTENTS_FILE_DESCRIPTOR(常量值為1)
- @Override
- public int describeContents() {
- return 0;
- }
- // 將對象寫入序列化結(jié)構(gòu)中,其中i標(biāo)識有兩種值,0或者1(PARCELABLE_WRITE_RETURN_VALUE)
- // 為1時表示當(dāng)前對象需要作為返回值返回,不能立即釋放資源,幾乎所有情況都為0
- @Override
- public void writeToParcel(Parcel parcel, int i) {
- parcel.writeInt(userId);
- parcel.writeString(userName);
- // 注意這里,并不是直接寫入boolean值,而是寫入整數(shù)值
- parcel.writeByte((byte) (isMale ? 1 : 0));
- // 此為不同之處2
- parcel.writeParcelable(book, i);
- }
- @Override
- public String toString() {
- return "User{ " +
- "userId = " + userId +
- ", userName = " + userName +
- ", isMale = " + isMale +
- "book = " + book.toString() +
- " }";
- }
- }
可以看出,結(jié)果已經(jīng)正確的打印了出來了:
注意:在 Parcelable 中,我們無法直接寫入 boolean 值,而是將其轉(zhuǎn)化為整數(shù)值進(jìn)行保存,這里為 Byte,當(dāng)然,你也可以使用 Int 等。