讀古今文學網 > Android程序設計:第2版 > Parcelable >

Parcelable

雖然Android框架支持Java序列化,但它通常不是封送程序狀態的最佳方式。Android自己的內部序列化協議Parcelable是輕量級的、高度優化的協議,只是稍有些難以使用。它是本地進程間通信的最佳方式。在P121「支持序列化的類」一節再次探討Parcelable對像時,會說明為什麼Parcelable對像不能用於存儲應用生命週期以外的對象。它們不是把狀態封送到數據庫或文件中的合適選擇。

以下是保存了一些狀態的非常簡單的對象。我們一起來看看它是如何遵循Parcelable協議的:


public class SimpleParcelable {
      public enum State { BEGIN, MIDDLE, END; }
      private State state;
      private Date date;
      State getState { return state; }
      void setState(State state) { this.state = state; }
      Date getDate { return date; }
      void setDate(Date date) { this.date = date; }
}
  

對像要遵循parcelable,必須滿足3個條件:

·必須實現Parcelable接口。

·必須有marshaler,它是接口方法writeToParcel的實現。

·必須有unmarshaler,一個名為CREATOR的public static final類型的變量,包含Parcelable.Creator實現的引用。

接口方法writeToParcel是marshaler。當需要把對像序列化為Parcel時,需要調用該方法。Marshaler的工作是寫下所有的信息生成對像狀態傳遞給Parcel。通常情況下,這意味著通過6種原始數據類型來表示對象的狀態:byte、double、int、float、long和String。以下還是該對象,但是它包含了marshaler:


public class SimpleParcelable implements Parcelable {
      public enum State { BEGIN, MIDDLE, END; }
      private static final Map<State, String> marshalState;
      static {
            Map<State, String> m = new HashMap<State, String>;
            m.put(State.BEGIN, \"begin\");
            m.put(State.MIDDLE, \"middle\");
            m.put(State.END, \"end\");
            marshalState = Collections.unmodifiableMap(m);
      }
      private State state;
      private Date date;
      @Override
      public void writeToParcel(Parcel dest, int flags) {
            // translate the Date to a long
            dest.writeLong(
                  (null == date)
                  ? -1
                  : date.getTime);
            dest.writeString(
                  (null == state)
                  ? \"\"
                  : marshalState.get(state));
      }
      State getState { return state; }
      void setState(State state) { this.state = state; }
      Date getDate { return date; }
      void setDate(Date date) { this.date = date; }
}
  

當然,writeToParcel的準確實現必然依賴於所要序列化的對象的內容。在這個例子中,SimpleParcelable對像包含兩種狀態,需要把這兩種狀態寫入傳遞的Parcel中。

為簡單的數據類型選擇表示方式通常不需要太多思考。例如,在這個例子中,2000年後的Date很容易通過時間表示。

但是,當選擇序列化的數據的表示方式時,要考慮其後期的變化。當然,在這個例子中,用int表示state,其值通過調用state.ordinal獲取,這種方式要簡單得多。然而,這種方式會使得該對像後期的兼容性維護變得很複雜。假設某一時刻需要在狀態State.BEGIN之前添加一種新的狀態State.INIT。這種微小的變化就會使得新版本的對象和之前版本的對象完全不兼容。同樣,如果類型稍弱一些,參數就可以使用state.toString來創建marshaled狀態表示方式。

在Parcel中,對象及其表示之間的映射是特定的序列化過程的一部分。它不是對象的內在屬性。通過不同的序列化程序,一個給定對像完全可能有全然不同的表示形式。為了說明這一原理(雖然顯得有點矯枉過正),如果State類是局部定義的,則對state執行marshal的map就是parcelable類的獨立、顯式定義的成員。

但是,如之前所示,編譯SimpleParcelable時並不會出現錯誤。它甚至還可以執行到parcel的marshale。但是,無法從parcel中再重新獲取到它。因此,需要unmarshaler:


public class SimpleParcelable implements Parcelable {
      // Code elided...
      private static final Map<String, State> unmarshalState;
      static {
            Map<String, State> m = new HashMap<String, State>;
            m.put(\"begin\", State.BEGIN);
            m.put(\"middle\", State.MIDDLE);
            m.put(\"end\", State.END);
            unmarshalState = Collections.unmodifiableMap(m);
      }
      // Unmarshaler
      public static final Parcelable.Creator<SimpleParcelable> CREATOR
            = new Parcelable.Creator<SimpleParcelable> {
                public SimpleParcelable createFromParcel(Parcel src) {
                    return new SimpleParcelable(
                        src.readLong,
                        src.readString);
                }
                public SimpleParcelable newArray(int size) {
                    return new SimpleParcelable[size];
                }
            };
      private State state;
      private Date date;
      public SimpleParcelable(long date, String state) {
            if (0 <= date) { this.date = new Date(date); }
            if ((null != state) && (0 < state.length)) {
                this.state = unmarshalState.get(state);
            }
      }
      // Code elided...
}
 

上面這段代碼只給出了新增加的unmarshaler代碼:public static final成員變量CREATOR及其相關的內容。CREATOR成員變量是Parcelable.Creator<T>實現的引用,其中T是要執行unmarshaled的parcelable對像類型(在這個例子中,即SimpleParcelable)。保證類型、拼寫都正確是非常重要的!如果CREATOR是protected類型而不是public類型,或者如果拼寫為Creator,Android框架都將無法執行對象的unmarshal。

Parcelable.Creator<T>的實現只包含一個方法createFromParcel,它從Parcel中unmarshal單個實例。實現這一點的傳統方式是從Parcel中讀取每個狀態,其順序和在writeToParcel中執行寫入的順序完全一致(再次強調,這一點很重要),然後把unmarshaled狀態傳遞給構造函數,調用該構造函數。因為包含unmarshaled狀態的構造函數是從類的作用域內調用的,故它的訪問屬性是package-protected,甚至是private。

支持序列化的類

Parcel API不局限於上一節提到的6個基本數據類型。Android文檔給出了parcelable支持的類型的完整列表,但是可以認為這些類型分為4組。

第一個分組是簡單類型,包括null、6個基本數據類型(int、float等),以及6個基本數據類型的封裝類(Integer、Float等)。

第二個分組是實現了Serializable或Parcelable的對象類型。這些對像不是簡單類型,但是知道如何執行序列化。

第三個分組是集合類型,包括數組array、列表list、map、bundle和前兩種類型的稀疏數組(int、float、ArrayList<?>、HashMap<String,?>、Bundle<?>、SparseArray<?>等)。

最後一個分組是一些特殊情況:CharSequence以及活動的對象(IBinder)。

雖然這些類型全部都可以marshale為Parcel,但需要避免兩種類型:Serializable和Map。如前所述,Android支持本地Java序列化。但是其實現效率沒有其他的Parcelable高。在對像中實現Serializable不是使其Parcelable的有效方式。相反,對像應該實現Parcelable接口,如在P118「Parcelable」一節所述,添加CREATOR對像和writeToParcel方法。如果對象的層次結構很複雜,這項工作會變得很繁重,但是它可以極大地提高性能,因此是值得的。

要避免的另一個Parcelable類型是Map。一般而言,Parcel實際上並不支持map,只支持那些關鍵字是string類型的。Android特有的Bundle類型提供了類似的功能(包含string關鍵字的map),而且是類型安全的。對像通過方法如putDouble和putSparseParcelableArray插入到Bundle中,每次插入一個parcelable類對象,其相應的方法如getDouble和getSparseParcelableArray負責從Bundle中獲取對象。Bundle就類似於map,只是它可以為不同的鍵存儲不同的對象類型,而且類型是絕對安全的。使用Bundle可以消除難以查找的錯誤,比如把一個序列化的float類型錯當成int類型。

類型安全也是人們更傾向於使用方法writeTypedArray和writeTypedList,而不是無類型的writeArray和writeList的原因之一。

AIDL和遠程過程調用

為了聲明AIDL接口,需要完成以下事情:

·創建一個描述API的AIDL文件。

·對於API中使用的每個複雜類型,為其定義一個Parcelable子類並在AIDL文件中把這種類型命名為parcelable。這種方式需要注意的是,必須把這些類的源分發給序列化這些類的所有客戶端。

·服務響應onBind方法,返回API stub的實現,onBind必須返回要調用的intent的正確實現。返回的實例提供真正的API方法的實現。

·在客戶端,實現ServiceConnection。onServiceConnected應該使用API.Stub.asInterface(binder)方法,對傳遞的binder進行轉換,把結果作為引用保存到服務API中。onServiceDisconnected必須把引用置空。客戶端調用bindService,包含API服務提供的Intent、ServiceConnection及控制服務創建的flag。

·綁定是異步的。bindService返回真並不意味著你有了服務的引用。必須釋放線程,等待onServiceConnected被調用之後才能使用該服務。

序列化和應用的生命週期

正如之前所述,Android應用可能無法使用虛擬內存。在一個小小的設備上,一個運行著的隱式應用無法為新的、可見應用騰出空間。然而,好的用戶體驗要求當用戶返回到一個應用時,希望該應用停留在它離開時的狀態。應用本身需要保存暫停時的狀態。幸運的是,Android框架使得保存狀態變得很簡單。

在P117「Java序列化」一節中給出的例子說明了支持應用保存暫停時狀態的通用框架機制。無論什麼時候從內存中刪除應用,都可以調用onSaveInstanceState方法,應用可以將任何必要的狀態寫入到Bundle中。當重新啟動應用時,Android框架會將這個Bundle傳遞到onCreate方法,因而應用可以重新得到其狀態。通過把內部數據緩存在ContentProvider中,把輕量級狀態(當前可見頁面)保存到onSaveInstance的Bundle中,應用可以恢復之前的執行,而不會中斷。

Android框架還提供了另一種工具來保存應用的狀態,即View類,它是任何在屏幕上可見的類的基類,其包含一個hook(鉤子)方法onSaveInstanceState,當從內存中刪除應用時會調用這個方法。實際上,它是從Activity.onSaveInstanceState調用的,這就是為什麼在你的應用中,該方法總是調用super.onSaveInstanceState方法的原因。

onSaveInstanceState方法支持以最小粒度保留狀態。舉個例子,一個電子郵件應用可能會使用它保留光標在未發送的郵件消息的文本中的精確位置。