Android開發之漫漫長途 X——Android序列化
該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡量按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑒了其他的優質博客,在此向各位大神表示感謝,膜拜!!!另外,本系列文章知識可能需要有一定Android開發基礎和項目經驗的同學才能更好理解,也就是說該系列文章面向的是Android中高級開發工程師。
前言
上一篇中我們比較詳盡的分析了ServiceManager。那麽本篇我們來講一下Android序列化的相關知識。為什麽跨度那麽大,因為“任性”?其實不是的,同誌們還記得上兩篇出現的Parcel嗎,Parcel是一個容器,他可以包含數據或者是對象引用,並且能夠用於Binder的傳輸。同時支持序列化以及跨進程之後進行反序列化,同時其提供了很多方法幫助開發者完成這些功能。從上面的描述可以看出Parcel是進程間通信的數據載體。我們常常需要持久化一些對象,除了數據庫等持久化方案之外,把對象轉換成字節數組並通過流的方式存儲在本地也是一個不錯的方法,另外當我們需要通過Intent和Binder傳輸數據是就需要使用序列化後的數據。
Java中的Serializable
Serializable 是Java所提供的一個序列化接口,它是一個空接口,為對象提供標準的序列化和反序列化操作。使用Serializable來實現序列化相當簡單,只需要在需要序列化的類實現Serializable接口並在其中聲明一個類似下面的標識即可自動實現默認的序列化過程。
public class Person extends PersonParent implements Serializable { private static final long serialVersionUID = 1L; //靜態域 public static int static_field; //transient域 public transient int transient_field; //一個普通的域 public String desc; public Person(String desc) { this.desc = desc; } static class PersonSerializableProxy implements Serializable{ private String desc; private PersonSerializableProxy(Person s) { this.desc = s.desc; } /** * 與writeReplace相同,ObjectInputStream會通過反射調用 readResolve()這個方法, * 決定是否替換反序列化出來的對象。 * @return */ private Object readResolve() { return new Person(desc); } } /** * * 在序列化一個對象時,ObjectOutputStream會通過反射首先調用writeReplace這個方法, * 在這裏我們可以替換真正送去序列的對象, * 如果我們沒有重寫,那序列化的對象就是最開始的對象。 * @return */ private Object writeReplace() { //序列化Person的時候我們並沒有直接寫入Person對象,而是寫入了PersonSerializableProxy對象 return new PersonSerializableProxy(this); } /** * 這裏主要是為了防止攻擊,任何以Person聲明的對象字節流都是流氓!! * 因為我在writeReplace中已經把序列化的實例指向了SerializableProxy * @param stream * @throws InvalidObjectException */ private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("proxy requied!"); } public static void main(String[] args) throws IOException, ClassNotFoundException { Person person = new Person("desc"); person.transient_field = 100; person.static_field = 10086; ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt")); outputStream.writeObject(person); outputStream.flush(); outputStream.close(); person.static_field = 10087; ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cache.txt")); Person deserialObj = (Person) objectInputStream.readObject(); System.out.println(deserialObj); } } class PersonParent{ private String name; //PersonParent類要麽繼承自Serializable,要麽需要提供一個無參構造器。 public PersonParent() { } public PersonParent(String name) { this.name = name; } }
不過在使用中也需要註意以下幾個問題:
- serialVersionUID用來標識當前序列化對象的類版本,建議每一個實現Serialization的類都指定該域。當然如果我們沒有指定,JVM會根據類的信息自動生成一個UID。
- 被transient描述的域和類的靜態變量是不會被序列化的,序列化是針對類實例。
- 需要進行序列化的對象所有的域都必須實現Serializable接口,不然會直接報錯NotSerializableException。當然,有兩個例外:域為空 或者域被transient描述是不會報錯的。
- 如果一個實現了Serializable類的對象繼承自另外一個類,那麽這個類要麽需要繼承自Serializable,要麽需要提供一個無參構造器。
- 反序列化產生的對象並不是通過構造器創建的,那麽很多依賴於構造器保證的約束條件在對象反序列化時都無法保證。比如一個設計成單例的類如果能夠被序列化就可以分分鐘克隆出多個實例...
Android中的Parcelable
相對於Serializable而言,Parcelable的使用要復雜一些
public class Book implements Parcelable {
private String name;
public Book(String name) {
this.name = name;
}
protected Book(Parcel in) {
name = in.readString();
}
//反序列化功能由CREATOR完成,在CREATOR的內部標明的如何創建序列對象和數組
public static final Creator<Book> CREATOR = new Creator<Book>() {
//從Parcel中反序列化對象
@Override
public Book createFromParcel(Parcel in) {
//其內部調用Parcel的一系列readXXX方法實現反序列化過程
return new Book(in);
}
//創建序列化數組
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
//序列化過程:
//重寫writeToParcel方法,我們要在這裏逐一對需要序列化的屬性用Parcel的一系列writeXXX方法寫入
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
}
從上述代碼註釋可以看出,寫一個實現Parcelable接口的類還是比較麻煩的,和Serailable相比,我們需要在writeToParcel中按序寫入各個域到流中,同樣,在createFromParcel中我們需要自己返回一個Book對象。
Parcelable在使用上也與Serializable稍有不同
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test2);
//獲取一個Parcel容器
Parcel parcel = Parcel.obtain();
//需要序列化的對象
Book book = new Book("c++");
//把對象寫入Parcel
parcel.writeParcelable(book,0);
//Parcel讀寫共用一個位置計數,這裏一定要重置一下當前的位置
parcel.setDataPosition(0);
//讀取Parcel
Book book1 = parcel.readParcelable(Book.class.getClassLoader());
Log.d("TestActivity",book1.toString());
}
}
Parcelable的寫
我們來看一下writeParcelable方法
[Parcel.java]
public final void writeParcelable(Parcelable p, int parcelableFlags) {
//判斷p是否為空
if (p == null) {
writeString(null);
return;
}
//① 先寫入p的類名
writeParcelableCreator(p);
//② 調用我們重寫的writeToParcel方法,按順序寫入域
p.writeToParcel(this, parcelableFlags);
}
public final void writeParcelableCreator(Parcelable p) {
//① 先寫入p的類名
String name = p.getClass().getName();
writeString(name);
}
Parcelable的讀
我們來看readParcelable方法
[Parcel.java]
public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
//① 調用readParcelableCreator
//這時獲得就是我們自定義的CREATOR
Parcelable.Creator<?> creator = readParcelableCreator(loader);
if (creator == null) {
return null;
}
// 判斷當前creator是不是Parcelable.ClassLoaderCreator<?>的實例
if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
//如果是的話,,我們調用reateFromParcel(this, loader);
Parcelable.ClassLoaderCreator<?> classLoaderCreator =
(Parcelable.ClassLoaderCreator<?>) creator;
return (T) classLoaderCreator.createFromParcel(this, loader);
}
//調用我們自定義的CREATOR中重寫的createFromParcel方法
return (T) creator.createFromParcel(this);
}
public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
//首先把類名讀取出來
String name = readString();
Parcelable.Creator<?> creator;
//mCreators做了一下緩存,如果之前某個classloader把一個parcelable的Creator獲取過
//那麽就不需要通過反射去查找了
synchronized (mCreators) {
HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
if (map == null) {
map = new HashMap<>();
mCreators.put(loader, map);
}
creator = map.get(name);
if (creator == null) {
try {
ClassLoader parcelableClassLoader =
(loader == null ? getClass().getClassLoader() : loader);
//加載我們自己實現Parcelable接口的類
Class<?> parcelableClass = Class.forName(name, false,
parcelableClassLoader);
Field f = parcelableClass.getField("CREATOR");
Class<?> creatorType = f.getType();
creator = (Parcelable.Creator<?>) f.get(null);
}
catch (Exception e) {
//catch exception
}
if (creator == null) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "non-null Parcelable.Creator object called "
+ "CREATOR on class " + name);
}
map.put(name, creator);
}
}
return creator;
}
我們的測試例子讀取Parcel
Book book1 = parcel.readParcelable(Book.class.getClassLoader());
可以看到我們在使用
readParcelable的時候,傳入的參數是Book類的類加載器,根據我們上面的代碼,我們知道我們先會通過反射獲取定義在Book類中的CREATOR屬性,我們回想一下在Book類中是怎麽定義CREATOR的
public static final Creator<Book> CREATOR = new Creator<Book>() {
//從Parcel中反序列化對象
@Override
public Book createFromParcel(Parcel in) {
//其內部調用Parcel的一系列readXXX方法實現反序列化過程
return new Book(in);
}
//創建序列化數組
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
我們得到CREATOR屬性後,調用它的createFromParcel方法,由多態可知調用的實際我們定義在CREATOR內的createFromParcel方法,在該方法內我們創建了Book對象(內部實現是通過Parcel的一系列readXXX方法)並返回。至此我們就得到了反序列化的對象
本篇總結
我們本篇詳細分析了Android序列化相關知識,你可以使用Java中的Serializable也可以使用Parcelable。
下篇預告
前面的文章中是對前面所講文章做一個小結。讀者敬請期待哦。
此致,敬禮
Android開發之漫漫長途 X——Android序列化