理解Windows Phone 7应用程序执行模型,墓碑机制,启动器和选择器及更多内容——Part 3

作者: Yochay Kiriaty

在前两篇文章(part 1 和part 2 )中你已经学习了应用程序生命周期中的不同事件——Launching, Deactivated, Closing和Activated以及它们之间的区别。在这些知识和这个代码 的基础上,我们继续向前探索。

保存临时数据和导航到正确的页面

回忆一下,我们程序的第二页包含两个文本框,一个用来允许用户输入一个将被保存到联系人信息中的电话号码,另一个让用户可以输入一条待发送的短信息。当我们在墓碑状态的程序中使用启动器(Launchers)和选择器(Choosers)时这将会变得十分有用。

在开始前首先要明确一下问题。最简单的方式是做一个小实验:

  1. 在Visual Studio中,打开你的程序。
  2. 导航至第二个页面并在两个文本框中输入一些信息。
  3. 这将会使你的程序被停用(关于此请参见这个系列的第二部分 )。
  4. 按一次返回键以回到你的应用程序。这会导致你的模拟器显示黑屏。
  5. 按下F5,或者用任何方法重启你的Visual Studio调试会话以重新激活你的程序。(再强调 一次,如果你错过了某些内容,请看这个系列的第二部分)
  6. 现在,你的程序应该处于运行状态并能看到程序的第二页。然而,文本框中的内容却是空的。在停用程序之前你所输入的内容已经不在了。它们消失了!

理解Windows Phone 7应用程序执行模型,墓碑机制,启动器和选择器及更多内容——Part 3_第1张图片

从这个小实验中你可以得知你在程序中输入的数据在程序停用(记住,你的程序被终止了)时不会自动被保存。在重新激活时就意味着重新开始了程序的一个新实例。这表明所有控件在默认情况下是没有数据的,除非你为它们加载数据。

有关墓碑程序最重要的用户可以不返回到你的应用程序 ,因此你的程序可能不会被重新激活 。如果你希望它发生,那么你需要将要恢复的任何数据保存到磁盘。当然作为一个开发人员这完全取决于你自己和你的工作职责去保存和获取应用程序的数据。

我们来区分一下这两种要保存的数据的类型(摘自MSDN ):

  • 持久数据 ——被多个程序的所有实例共享的数据。持久数据的保存和载入都在独立存储区 。在不同应用程序执行时各个程序的设置就是持久化数据的一个例子。
  • 临时数据 ——描述一个应用程序单个实例状态的数据。临时数据存储在PhoneApplicationService 类的State 字典中。一个墓碑程序在重新被激活时会恢复它的临时状态。

在下篇文章中我们会讲解持久数据如何与独立存储区一同工作。现在让我们重点关注一下如何管理临时数据和使用State字典。

在SDK中的一个新类就是PhoneApplicationService 。它提供了对应用程序生命周期中的各种状态的访问。这包含程序闲置行为的管理和程序状态的管理。这个类在墓碑这个游戏中扮演了主角,因为它对外公开了Launching, Deactivate, Activated和Closing事件,它们在App.xaml.cs文件中有对应的方法(你已经见过了)。这个类还包括一个只读的类型为IDictionary 的State属性。这个字典的重要性在于当你的程序被墓碑化处理后它会被Windows Phone操作系统持久化在你的应用程序中。当应用程序被重新激活,存在字典中的对象会被返回。如果你只是想让应用程序从墓碑状态返回那么你不用将这些临时对象保存到磁盘上。因此,如果你想使用State字典,要确保只在其中保存临时数据——那些你不介意丢失或只对当前应用程序实例有用的数据。在我们的例子中,我们保存电话号码和短信息的内容。

请注意你保存到这个字典中的对象必须是可序列化的 (serializable),否则你会在停用事件触发时得到一个关于操作系统无法禁用或恢复使用你的对象的异常。

MSDN中的最佳实践 建议你在 OnNavigatedFrom 事件中将临时的页面数据保存在State字典中,并且在OnNavigatedTo 事件中载入数据。

我更新了程序从而可以保存电话号码和短消息到这个字典中,并在每次导航到页面时载入它。如果一个“完整的”新的程序实例(意思是Launching事件被触发)启动时,State字典是空的。

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) { Util.Trace("***** in DetailsPage: OnNavigatedFrom ( " + DateTime.Now.Ticks + " *****)"); //try to locate the phone number from previous save and simply override it if (true == PhoneApplicationService.Current.State.ContainsKey(PhoneNumberKey)) { //clear prev value PhoneApplicationService.Current.State.Remove(PhoneNumberKey); } PhoneApplicationService.Current.State.Add(PhoneNumberKey, this.PhoneNumberTxt.Text); //try to locate the SMS Messagefrom previous save and simply override it if (true == PhoneApplicationService.Current.State.ContainsKey(SmsMessageKey)) { //clear prev value PhoneApplicationService.Current.State.Remove(SmsMessageKey); } PhoneApplicationService.Current.State.Add(SmsMessageKey, this.MessageTxt.Text); } // Step 2 protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) { Util.Trace("***** in DetailsPage: OnNavigatedFrom ( " + DateTime.Now.Ticks + " *****)"); //try to locate the phone number from previous run if (true == PhoneApplicationService.Current.State.ContainsKey(PhoneNumberKey)) { string s = PhoneApplicationService.Current.State[PhoneNumberKey] as string; if (!String.IsNullOrEmpty(s)) { Util.Trace("***** in DetailsPage: OnNavigatedTo: Found phone number ( " + DateTime.Now.Ticks + " *****)"); this.PhoneNumberTxt.Text = s; } } // Step 2 //try to locate the phone SMS MSG from previous run if (true == PhoneApplicationService.Current.State.ContainsKey(SmsMessageKey)) { Util.Trace("***** in DetailsPage: OnNavigatedTo: Found Sms Msg ( " + DateTime.Now.Ticks + " *****)"); string s = PhoneApplicationService.Current.State[SmsMessageKey] as string; if (!String.IsNullOrEmpty(s)) { this.MessageTxt.Text = s; } } }

注意以下的变化,页面2的信息在页面之间导航切换时被保存。也就是你在页面2按下返回键,应用程序返回到页面1的时候。如果你在页面1点击“导航到下一页”按钮,你的程序会导航到页面2。OnNavigateTo事件(在页面2中)会被触发,同时上面的代码会使控件加载数据。这非常重要因为如果你仔细观察Visual Studio输出窗口的信息将会发现每次你导航到页面2时,它的构造函数都会执行。这说明每次在页面2中按下返回键时,该页会被销毁,因此你每次从页面1导航到页面2时,页面2都重新被创建。如果想观察这个行为,可以在两个页面中不断的切换,同时注意每次从页面1导航到页面2时页面2的构造函数都会被调用。如果在程序中将OnNavigatedFrom方法中的内容注释掉,那么在页面间切换时,你会看到第二页的信息没有被保存。

理解Windows Phone 7应用程序执行模型,墓碑机制,启动器和选择器及更多内容——Part 3_第2张图片

现在将程序变为墓碑你会看到电话号码和短信息已被保存,不只是在页面切换时,还在离开程序后返回时发生。导航到页面2,输入一个电话号码和一些文本信息,按下Windows键使程序变为墓碑。你应该可以能看到Deactivated事件的跟踪信息然后程序被终止掉。再按下返回键同时不要忘记在Visual Studio中按F5重启调试会话。你的程序将会返回到墓碑状态同时你会看到Activated事件被触发,然后是页面2的构造函数。接着你会看到OnNavigatedTo的信息,如果一切顺利,你会看到"Found phone number"和"Found Sms Msg"两行信息,正如下图显示的那样。

理解Windows Phone 7应用程序执行模型,墓碑机制,启动器和选择器及更多内容——Part 3_第3张图片

现在一切都很好,我还希望解释一下墓碑化Windows Phone应用程序的工作方式。不过现在,是时候开始我们的游戏了,同时在实战中展示一些很酷的内容。

首先, 我添加了一个助手类Logger用来记录所有的跟踪信息并把它们显示在主页面(页面1)的文本框中。Logger的目的向你证明你的应用程序最终被终止掉同时State字典的的确确是在工作。这个日志类还可以让你在模拟器的非调试模式下运行应用程序,同时可以获取跟踪信息并显示在日志文本框中。

Logger类

Logger类实现了一个非常简单的单例模式 (不过可能不是线程安全的)。这个类设计成单例主要是当你的应用程序结束时只有一个类的实例从内存中被移除,除此之外还有相关的事件和在不同事件中加载的数据。这个类有一个string成员log,还有一个DataTime成员用来保存Logger对象的创建时间。通过Logger类你可以向日志添加一些新行还可以获取整个日志。这非常有利于调试,这也正是Logger的职责所在。每次你使用Util类添加一个跟踪时,你都可以将它加入到Logger中。

通常单例的实现没有公共的构造函数。然而如果你要将这个类保存到State子典中,这意味着Logger类必须是可序列化的,因此必须有一个公共的默认(空的)构造函数。否则你在停用或激活程序时会得到一个异常。

为了“查看”日志,我在MainPage.xaml.cs中加入了OnNavigatedTo并将日志中的文本信息加载到主页面的logTextBox 控件。因此每次你导航到程序的主页面中都会看到日志文件被打印出来。这可以使你看到程序的跟踪信息而不需要在VS中调试程序(感谢的Jaime Rodriguez 的建议)。现在来试试。

  1. 首先需要部署你的程序到模拟器。你可以在Visual Studio的解决方案资源管理器面板中右击你的项目,或者从生成菜单中点击“部署”。
  2. 在模拟器中,点击电话右上角白色的箭头导航到应用程序列表。
  3. 你应该会看到一个非常短的应用程序列表。从列表中找到你的程序。如果你使用了本篇文章中的代码,则应用程序的名字是LWP.AppLifeCycle。
  4. 应用程序启动后,你会在主页面文本框中看到跟踪信息。你可能想改变电话的设置(通过点击扳手按钮)来调整模拟器的缩放等级到100%。日志文本框中的文本很小从而可以显示尽可能多的历史记录而无需滚动条。
  5. 在日志文本框中你会看到应用程序创建和主页面构造函数的跟踪信息。
  6. 点击Next按钮。
  7. 在第二页中,输入一个电话号码或一些信息。
  8. 然后点击Windows键停用你的程序。在模拟器中,你应该会看到起始界面。
  9. 点击返回键回到你的应用程序。这会重新激活你的应用程序并恢复程序到最后一个浏览过的页面,也就是第二页,如果一切顺利,你会看到在步骤7中输入过的信息。
  10. 在第二页中,点击返回键回到第一页。在日志文本框中看到的跟踪信息应与下图一致:

理解Windows Phone 7应用程序执行模型,墓碑机制,启动器和选择器及更多内容——Part 3_第4张图片

跟踪信息中最有意思的一段是在虚线中的,下面的代码片段来自应用程序的Activatedd事件。

if (true == PhoneApplicationService.Current.State.ContainsKey(LoggerKey)) { Logger logger = (PhoneApplicationService.Current.State[LoggerKey] as Logger); long timeDef = Logger.Instance.CreationTime.Ticks - logger.CreationTime.Ticks; Util.Trace("-------------------------------------/n--> Time difference between Loggers = " + timeDef + "/n" + logger.GetLog() + "/n-------------------------------------"); }

在这段代码中,你会看到我们在State字典中检索Logger对象。找个对象在Deactivated事件触发时被放到了State字典中。假设Logger对象被找到,我们创建一个临时的叫logger的对象(注意这不是我们在当前程序中真正使用的logger对象)。然后对比从字典中获取的logger对象和刚刚激活的应用程序中的logger对象的创建时间。正如你看到的,存在一个时间差。我们在停用程序时保存到State字典中的logger比新logger的时间长。在代码中你可以看出我们不能真正的初始化旧的logger;它的创建时间和日志都被恢复并打印到文本框中。所以虚线中的每一行信息都取决于应用程序上一次运行时停用事件的触发情况。

虚线后的第一行显示了第二页的构造函数,不出所料,这表明“新的”应用程序被激活并从我们停用的应用程序中返回到第二页。

总结

现在你已经在实践中见过了全部的4个事件:Launching, Deactivated, Activated和Closing。我希望你能清楚程序在何时没有运行,在何时终止以及如果你的数据没有保存它在何时会丢失。并且在你返回程序时,无论触发的是Activated事件或是Launching事件,你都会得到一个程序的新实例(我们的单例小实验已经证实了这一点)。

State字典可以在Deactivated和Activated事件中保存临时数据,而且在我们要讨论选择器和启动器的下篇文章中它被证实是一个非常有用的工具。

你可能感兴趣的:(工作,windows,windows,String,sms,phone,电话)