移动产品相关问题

Windows Phone 7 逻辑删除

Jaime Rodriguez

下载代码示例

优秀移动平台应承认移动性对设备施加的种种硬件约束。 与桌面计算机相比,移动设备内存更少、处理能力更低并且屏幕空间和电池寿命有限。 综合考虑所有这些约束,您应当得出结论:在将会运行许多应用程序的非专用设备上,最终将关闭一些应用程序,从而为其他应用程序提供资源。

Windows Phone 通过名为“逻辑删除”的功能来应对这种约束。 虽然逻辑删除的概念看起来简单易懂,实际上它却是开发人员争论的焦点。 有人认为不需要这种功能。 有人认为这种功能太难实现。 其他人则是不喜欢这种功能的名称。 移动设备约束仍然是相当棘手的问题,因此,出色的移动应用程序必须能够处理逻辑删除。

Windows Phone 应用程序生命周期

大多数 Windows Phone 开发人员新手在想起此平台时,都预期应用程序具有如下所示的生命周期: 

  1. 开始
  2. 运行
  3. 退出
  4. 返回 1 并重新开始

Windows Phone 7 通过公开不同的生命周期(更少地面向流程,更多地面向会话)向这种预期发起了挑战。

在 Windows Phone 7 中,应考虑如下所示的生命周期:

  1. 开始
  2. 运行
  3. 中断执行或退出
  4. 如果中断则返回 – 或者即使中断也重新开始
  5. 如果退出则重新开始 

此面向会话的新模型的优点是,用户可以在应用程序中导航,而不必考虑操作系统管理其资源的方式。 用户不关心中断游戏来回复收到的短信是否会终止游戏的进程。 用户的期望是回完短信后可以回到游戏中。 如果期望能够很好地实现,则背后的细节无关紧要。

而不利于开发人员的方面是,要真正提供会话连续的感觉,还要完成许多处理工作,因为会话仍然在以进程为中心的传统操作系统上运行。 若要适合以进程为中心的环境中的会话,您可以为会话创建逻辑状态:已启动、已激活、正在运行、已停用、已逻辑删除和已关闭(或已结束)。

图 1 显示 Windows Phone 7 应用程序的实际生命周期。 图 2 中介绍了应用程序生命周期事件,这些事件由 Microsoft.Phone.Shell.PhoneApplicationService 类公开。

Windows Phone 7 应用程序生命周期

图 1 Windows Phone 7 应用程序生命周期

图 2 应用程序生命周期事件

逻辑状态 PhoneApplicationService 事件 说明
已启动 Launching 如果用户按开始图标或应用程序列表图标,或者单击 toast 通知,将启动应用程序。 “已启动”表示会话重新开始。
已激活 Activated 如果用户按“后退”按钮,并在前台重新打开以前已停用的应用程序,将激活应用程序。 在这种情况下,用户期望返回正在进行的会话。
正在运行 Running 启动或激活后,应用程序将运行。 
已停用 Deactivated 当前台处理从此应用程序转移到另一个应用程序或操作系统组件(例如选择器、启动器或锁定屏幕)时,将停用正在运行的应用程序。 会话将中断,但应该会在稍后还原。
已结束(或已退出) Closing

用户按主页上的“后退”键即表示退出应用程序。

退出后,用户期望返回到另一个全新的应用程序。

“已逻辑删除”状态有点复杂,不直接与 PhoneApplicationService 事件相关。 停用某个应用程序时,操作系统不会立即终止该应用程序的进程。 理论上,应用程序停用后,当操作系统需要资源时才会终止该应用程序。 应用程序根本不会得到通知,它只是被终止。

实际上,在控制权转至另一个前台应用程序后,Windows Phone 7 会快速终止进程,但这不是您应考虑的细节。 Microsoft 已经于今年二月在移动通信全球世界大会上宣布即将推出快速应用程序切换等改进,因此,不要根据实现细节来确定何时会进行逻辑删除。 相反,通过在停用时执行正确的工作可为逻辑删除做好准备。

停用和 逻辑删除

电话一直有许多进程(shell、电话等)在运行,但最多有一个应用程序在前台运行。 (如果没有任何应用程序在前台运行,则这个数字为零。)

当前台应用程序将控制权转至另一个应用程序或操作系统组件时,该前台应用程序会停用。 在某个进程停用后,操作系统可能会终止该进程以释放资源。 这称为逻辑删除。

如您所见,并非在每次停用应用程序时都会发生逻辑删除,但逻辑删除始终在停用之后发生。 事实上,Deactivated 是 PhoneApplicationService 在进行逻辑删除前触发的最后一个事件,因此,您必须在此处执行工作以便稍后重新激活应用程序。

图 3 显示可导致停用的所有不同任务,并猜测发生逻辑删除的可能性。 

图 3 停用任务

操作 能否停用? 能否逻辑删除?
用户按应用程序首页上的“后退”按钮 否;这将关闭应用程序 否。 从未发生停用。
用户按“开始”按钮 很可能,但不保证。 在应用程序停用后的几秒钟将对其进行逻辑删除。 如果用户在应用程序停用后很快又返回该应用程序,则它可能不会被逻辑删除。 这可能被视为不确定超时。
用户调用已逻辑删除的选择器或启动器 很可能,但会发生超时。
用户调用未逻辑删除的选择器或启动器 不太可能,但仍可能发生。 如果用户在执行任务的过程中按“开始”按钮,则会看到“用户按‘开始’按钮”规则。 不会触发新 Deactivated 事件,因为应用程序已停用。
出现锁定屏幕,但应用程序未配置为在锁定下运行 很可能,但会发生超时。
出现 toast 通知且用户点击该通知,转移到另一个前台应用程序 很可能,但会发生超时。

有一组不会立即发生逻辑删除的选择器子集,但如果用户采取对进程进行逻辑删除的操作,仍可能会发生逻辑删除。 这些操作包括 PhotoChooserTask(除非用户指定裁剪)、CameraCaptureTask、MediaPlayerLauncher、EmailAddressChooserTask 和 PhoneNumberChooserTask。

所有其他选择器和启动器在调用 Show 方法之后立即进行逻辑删除。

若要查看 Windows Phone 7 应用程序生命周期实际效果,请启动代码下载部分中的 LWP.TombStoning 示例。 

保存和还原状态

由于基于会话导航的目标是便于用户无缝跳过前台应用程序,因此,您必须将所有相关状态保存到 Deactivated 事件中,并从 Activated 事件中还原状态。 大多数应用程序都具有三种状态可进行管理:

  • 持久应用程序状态:必须始终保存这种状态。 这包括应用程序设置、用户数据等。
  • 特定于会话的应用程序状态:包括临时状态(例如缓存)和 ViewModel(需要在激活过程中还原,但在重新启动应用程序时重新启动)。
  • 特定于 UI 或页面的状态:需要这种状态才能在激活应用程序时还原 PhoneApplicationPage。 Windows Phone 7 在进行逻辑删除时保存应用程序的 Back 堆栈。 在激活后,它仅还原在对应用程序进行逻辑删除之前的最后一个活动页面。 如果用户按“后退”按钮,系统将实例化上一页面。

持久应用程序状态应保存在 IsolatedStorage 中或通过 ApplicationSettings 类保存。 应尽早保存应用程序状态,以防电池电量耗尽(举例来说)。

如果要保存的内容是用户数据(例如会话缓存),并且您不想太频繁地序列化,则应同时将状态保存在 Deactivated 和 Closing 事件中,(对应地)应从 Activated 或 Launching 事件中还原该状态。

可以将特定于会话的状态保存在独立存储中(如果您要控制序列化格式或者拥有过多数据),也可以将该状态保存在 PhoneApplicationService.State 字典中。 只应将该状态保存在 Deactivated 事件中并从 Activated 事件还原。

特定于页面的状态应保存在 PhoneApplicationPage.State 字典中。 保存页面状态的关键在于,记住应用程序具有一些页面的 Back 堆栈,这些页面将在发生 PhoneApplicationService.Deactivated 时自动序列化。 若要使页面做好逻辑删除准备,您必须侦听页面中的 PhoneApplicationPage.OnNavigatedFrom 重写,并将尚未提交的任何视图状态保存到页面字典中的 Model(或 ViewModel)。 不要等到 Deactivated 事件发生,因为那时您将无法在 Back 堆栈中访问页面。

当然,如果您在收到 OnNavigatedFrom 时保存页面状态,则应在 OnNavigatedTo 重写中还原该页面的状态。

您还可以将特定于页面的状态保存到 ViewModel,然后将 ViewModel 序列化为会话状态,但这需要在 ViewModel 上保存未提交的状态,因此我不建议这样做。 利用已有基础结构保持先进状态,以便以后在平台上进行优化。

绕开陷阱

逻辑删除并不难。 只是工作起来有点枯燥乏味,并且需要一致性和规划。 如果您的应用程序已停用但未逻辑删除,则状态将保留在内存中且不会重建。

避免依赖这样的类构造函数:它们创建应用程序所需的状态,但在停用期间可能会释放它们。 对称性是首选要求。 对应用程序级状态使用 PhoneApplicationService Deactivated 和 Activated 事件,对页面状态使用 OnNavigatedFrom 或 OnNavigatedTo。

如果应用程序中有对象(单例)在激活调用的外部进行了实例化(可能是因为延迟实例化),则在尝试使用这些对象之前,始终需要检查是否已正确构造和初始化这些对象。 我遇到过的一个常见错误是,在 PhoneApplicationService.Activated 事件或 PhoneApplicationPage.OnNavigatedTo 中读取数据但不重置数据。 可以多次对页面执行 NavigatedTo(无论是否进行逻辑删除),甚至可以在会话期间多次对会话进行逻辑删除。

还原状态之后,请清除它。 以后可以在 NavigatedFrom 重写中设置页面的状态,或者在 Deactivated 事件中设置应用程序的状态。

应巧妙地设置保存的内容和还原时间。 要使用户无缝回到应用程序,需要迅速还原应用程序。 如果您保存太多页面状态或应用程序状态,则会降低激活速度。 根据需要,对激活应用程序时可能不是立即需要的后台加载状态使用独立的存储。 激活和停用都应在不到 10 秒的时间内发生。 请注意这一点,否则,在进程完成停用之前或在重新激活进程时,操作系统可能会终止进程。 但您的目标应该是在远少于 10 秒的时间内完成操作。

了解序列化框架的约束。 对于您所有的页面状态和应用程序状态,您最多可以存储大约 2MB 的数据。 如果总数据量超过这一限制,则您在导航和停用时将会看到异常。 不应在页面状态下序列化这样大量的数据。 如果您需要缓存大型数据集,请将它们保存在独立的存储中。

使用查询字符串进行页面导航。 如果您必须将上下文传递到新页面,请使用传递到该页面的查询字符串来传递所有数据,或者将唯一标识符(标记)传递到可根据该标记提取数据的服务定位器。 不要假定在逻辑删除后可以在激活页面时使用 ViewModel 或页面状态。

了解选择器和启动器。 并非所有选择器和启动器都进行逻辑删除,并且有一些有关如何关联选择器的事件侦听器的特定规则。 对于这些规则,请阅读“如何:对 Windows Phone 使用选择器”,网址为:bit.ly/edEsGQ

请留意 OnNavigatedTo 和 PhoneApplicationPage.Loaded 之间的关系。 在 OnNavigatedTo 中,尚未完全构建页面的可视树。 通常,您必须提取已还原状态,但要等到页面的 Loaded 事件还原 UI 状态。 必须延迟的操作示例包括设置 Focus、在数据透视表中设置 SelectedIndex 以及滚动。

如果您执行大量操作(不应这样做)来保存和还原数据,请考虑采用某些高级优化。 请注意,仅在必要时才这样做,并应确保进行全面测试。

若要检测逻辑删除,请在 Deactivated 中对应用程序类设置一个标记。 如果该标记在执行 Activated 时未重置,则意味着您未进行逻辑删除,所有页面仍应保留在内存中且不需要还原。 若要结合运用该标记与检测在页面中进行逻辑删除,则可以为会话中的每个激活使用一个标记。

另一个优化是侦听页面的 OnNavigatingFrom 重写并检测方向。 如果返回 NavigationMode,页面将被销毁,因此也就无需再保存其状态了。

同样,规划是正确进行逻辑删除的关键。 不要等到应用程序开发周期结束后才想到逻辑删除,也不要尝试对它进行翻新。 请及早规划,随时实现并全面测试。

进一步完善

对开发人员的最后一个提示是认真考虑无缝体验。 使用 Windows Phone 7 控件可以轻松处理一个页面。 为使用户真正地获得无缝操作的体验,感觉他从未离开页面或应用程序,您应考虑还原以下内容:

  • 数据透视表的 SelectedIndex(如果页面包含数据透视表)
  • ListBox 中的滚动位置或页面中的任何其他 ScrollViewer
  • 地图控件中的缩放级别和其他转换,图片查看器或任何其他支持操作的控件
  • TextBox 中的未提交文本;如果 TextBox 对于应用程序至关重要(例如 Twitter 应用程序中的 tweet 文本),请考虑还原文本选择、插入符号位置和焦点
  • 页面上具有焦点的元素(尤其对于 TextBox,它需要显示 SIP)

不应还原全景图中的 SelectedItem。 全景图不支持这一点,并且在全景图中设置 DefaultItem 与平移到正确的页面不同。 建议您避免使用 DefaultItem 返回在进行逻辑删除之前选择的全景图项目。

若要查看这些提示的实际效果,请启动代码下载部分中的 LPW.TombstoningWithState 示例。 readme.txt 文件中包含每个方案的指针和脚本。

最后的一些备注

本文绝不是逻辑删除的全面参考材料。 但既然您知道要查找什么,请开始在自己的 Windows Phone 7 应用程序中引入一些逻辑删除模式。 我想您立刻就会感觉到这些模式改进用户体验的程度。

Jaime Rodriguez 是 Microsoft 的主要推广专家,从事推动新兴客户端技术(如 Silverlight 和 Windows Phone 7)的普及工作。您可以通过他在 twitter.com/jaimerodriguez 的 Twitter 与他联系,或通过 blogs.msdn.com/jaimer 访问其博客。

衷心感谢以下技术专家对本文的审阅:Peter Torr