首页 » Android程序设计:第2版 » Android程序设计:第2版全文在线阅读

《Android程序设计:第2版》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方法支持以最小粒度保留状态。举个例子,一个电子邮件应用可能会使用它保留光标在未发送的邮件消息的文本中的精确位置。