如何保留和还原 Windows Phone 应用程序状态

2012/2/9

Windows Phone 执行模型一次仅允许一个应用程序在前台运行。当用户导航离开应用程序时,该应用程序通常会置于休眠状态。在休眠状态下,应用程序代码不再执行,但是该应用程序仍会保留在内存中。当用户按“返回”按键返回到休眠的应用程序时,该应用程序将恢复运行并且其状态会自动还原。但是,在用户导航离开后,应用程序可能会被逻辑删除。如果用户导航回已逻辑删除的应用程序,则该应用程序必须还原其自身的状态,因为它不再处于内存中。PhoneApplicationService 类提供了四个可帮助您保留和维护应用程序状态的事件:LaunchingActivatedDeactivatedClosing。这些事件使您的应用程序有机会还原由多个应用程序页面使用的任何全局应用程序数据。例如,身份验证密钥或 Web 服务查询的结果就是此类型的数据。本主题演示有关使用这些事件保存和还原应用程序状态的模式。有关应用程序逻辑删除的更多信息,请参阅 Windows Phone 执行模型

手机应用程序普遍使用从网络资源(如 Web 服务)获取的数据。应用程序内的多个页面通常使用这一来自网络的数据。该数据可视为应用程序状态的一部分。当取消激活和重新激活您的应用程序时,该数据将会丢失,除非您的应用程序已将其存储在设备上。您的应用程序只需对网络资源再进行一次查询,便可再次获取该数据,但是有两种方法可让您在取消激活应用程序时存储状态数据,并在重新激活应用程序时还原该数据。第一种方法是使用永久存储区。这包括独立存储和一个本地数据库。PhoneApplicationServiceState 字典是一个临时存储位置,它只有在应用程序被逻辑删除时才存在,但是其访问速度要比永久存储区快得多。正确使用这两种存储类型,可大大改善用户体验和应用程序的加载时间。

重要说明重要说明:

State 字典中存储的任何数据都必须可序列化,无论是直接序列化还是使用数据协定序列化。有关更多信息,请参阅使用数据协定

本主题将指导您创建一个应用程序,该应用程序可显示来自 Web 的数据。当应用程序被逻辑删除和终止时,该数据会随着应用程序的启动、取消激活、逻辑删除和重新激活将保留、还原和检索。所有耗时的数据操作都将异步执行,以便应用程序的用户界面可持续保持响应。此示例中使用的应用程序状态数据是一个从网站上获取的简单数据字符串,该数据显示在页面上的 TextBlock 中。实际的应用程序通常会使用更加结构化的数据和更加复杂的用户界面,但是基本概念是相同的。

本主题将分两部分指导您实现应用程序状态管理。首先,将对主应用程序类进行修改以处理应用程序状态事件。接着,将介绍如何实现 PhoneApplicationPage 对象,该对象使用保留的应用程序状态。

此部分将指导您对主应用程序类进行必要的更改以实现示例应用程序。

修改应用程序类

  1. 在 Visual Studio 中,创建一个新的“Windows Phone 应用程序”项目。此模板在“Silverlight for Windows Phone”类别中。

  2. 此部分中的所有步骤都将修改所有“Silverlight for Windows Phone”项目模板中包含的 App.xaml.cs 文件。首先,将以下 using 指令添加到文件顶部。

    using System.Threading;
    using System.IO;
    using System.IO.IsolatedStorage;
    
    
  3. 以下代码创建公共属性 ApplicationDataObject,它将用于访问应用程序数据,在此示例中即为简单的字符串对象。该属性使用私有变量 _applicationDataObject 来存储数据。同时,还创建了 ApplicationDataObjectChanged 事件和 OnApplicationDataObjectChanged,以允许应用程序内的页面在应用程序数据发生更改时接收事件并重新获取其状态。将以下代码粘贴到 App.xaml.cs 的 App 类定义中。

    // Declare a private variable to store application state.
    private string _applicationDataObject;
    
    // Declare an event for when the application data changes.
    public event EventHandler ApplicationDataObjectChanged;
    
    // Declare a public property to access the application data variable.
    public string ApplicationDataObject
    {
      get { return _applicationDataObject; }
      set
      {
        if (value != _applicationDataObject)
        {
          _applicationDataObject = value;
          OnApplicationDataObjectChanged(EventArgs.Empty);
        }
      }
    }
            
    // Create a method to raise the ApplicationDataObjectChanged event.
    protected void OnApplicationDataObjectChanged(EventArgs e)
    {
      EventHandler handler = ApplicationDataObjectChanged;
      if (handler != null)
      {
        handler(this, e);
      }
    }
    
    
  4. 在此示例中,ApplicationDataObject 属性用于向页面公开应用程序数据。单独的属性 ApplicationDataStatus 用于公开应用程序数据的源,无论它是来自 Web、独立存储,还是应用程序的 State 字典。将以下属性定义粘贴到上一步骤中的代码下方。

    // Declare a public property to store the status of the application data.
    public string ApplicationDataStatus { get; set; }
    
    
  5. 现在,实现应用程序事件处理程序。这些事件处理程序的存根 (Stub) 由所有“Silverlight for Windows Phone”项目模板中的 App.xaml.cs 提供。当用户最初启动应用程序(例如通过点按手机应用程序列表中的应用程序图标)时,会引发 Launching 事件。此示例将该事件处理程序保留为空,并在代码中的其他位置处理应用程序状态的加载。您的应用程序可以在此 Launching 中执行代码,但是在该事件处理程序中执行任何代码都将延迟应用程序的初始启动。鉴于此原因,您绝不应尝试在此事件处理程序中访问任何耗时资源。这包括访问独立存储、访问数据库存储,或尝试访问 Web 服务。所有应用程序事件都限定在 10 秒的时间内完成。如果您的应用程序的任何事件超出此限制,应用程序将会立即终止。

    // Code to execute when the application is launching (for example, from Start)
    // This code will not execute when the application is reactivated.
    private void Application_Launching(object sender, LaunchingEventArgs e)
    {
    }
    
    
  6. 在应用程序处于休眠状态或被逻辑删除时,如果用户导航回该应用程序,则会引发 Activated 事件。与 Launching 事件类似,在此事件中执行代码将会延迟应用程序恢复。请勿在此处理程序中访问独立存储、访问数据库存储,或执行同步 Web 请求。ActivatedEventArgsIsApplicationInstancePreserved 属性可使您的应用程序了解应用程序是正从休眠状态中返回,还是已被逻辑删除。这主要用于 XNA Framework 应用程序,因为这些应用程序不接收页面级别的 Silverlight 事件。在此示例中,如果 ApplicationInstancePreserved 为 true,则表示该应用程序已休眠,因此其状态自动进行了保留,ApplicationDataStatus 属性已更新并且处理程序退出。接下来,处理程序会检查 State 字典中是否有可用的应用程序数据。如果有,则表示在 Deactivated 事件过程中,应用程序已被逻辑删除并在此处保存了状态,ApplicationDataObjectState 字典进行了填充,并且数据状态属性进行了更新。请将现有的 Application_Activated 处理程序替换为以下代码。

    // Code to execute when the application is activated (brought to the foreground)
    // This code will not execute when the application is first launched.
    private void Application_Activated(object sender, ActivatedEventArgs e)
    {
      if (e.IsApplicationInstancePreserved)
      {
        ApplicationDataStatus = "application instance preserved.";
        return;
      }
    
      // Check to see if the key for the application state data is in the State dictionary.
      if (PhoneApplicationService.Current.State.ContainsKey("ApplicationDataObject"))
      {
        // If it exists, assign the data to the application member variable.
        ApplicationDataStatus = "data from preserved state.";
        ApplicationDataObject = PhoneApplicationService.Current.State["ApplicationDataObject"] as string;
      }
    }
    
    
  7. 每当用户向前导航离开应用程序时,都会调用 Deactivated 事件。虽然应用程序通常会在取消激活后置于休眠状态,但是此时没有任何方法可以知道应用程序在此事件后将被逻辑删除还是终止。鉴于此原因,您应当将应用程序状态保存在 State 字典以及独立存储或数据库存储中。此示例应用程序将 ApplicationDataObject 保存到 State 和 IsolatedStorage 中。SaveDataToIsolatedStorage 方法是一种帮助器方法,在本主题的后面部分将对其进行定义。与所有应用程序事件类似,如果您的应用程序完成此处理程序所花的时间超过 10 秒,则应用程序将会终止。鉴于此原因,我们建议您在应用程序的整个生存期内以增量方式保存状态。此事件只不过是保存任何未保存数据的最后机会。请将以下代码粘贴到 App.xaml.cs 中现有 Deactivated 处理程序的上方。

    // Code to execute when the application is deactivated (sent to background)
    // This code will not execute when the application is closing.
    private void Application_Deactivated(object sender, DeactivatedEventArgs e)
    {
      // If there is data in the application member variable...
      if (!string.IsNullOrEmpty(ApplicationDataObject))
      {
        // Store it in the State dictionary.
        PhoneApplicationService.Current.State["ApplicationDataObject"] = ApplicationDataObject;
    
        // Also store it in isolated storage, in case the application is never reactivated.
        SaveDataToIsolatedStorage("myDataFile.txt", ApplicationDataObject);
      }
    }
    
    
  8. 当用户使用“返回”按键向后导航经过应用程序的第一个页面时,会引发 Closing 事件。在此事件之后,您的应用程序将会终止。若要返回到您的应用程序,用户必须重新启动它。鉴于此原因,任何状态数据都应保存到独立存储,但是没有必要将其保存到 State 字典。再次强调,如果您的应用程序完成此事件所花的时间超过 10 秒,则应用程序将会立即终止,因此最好的方法是在应用程序的整个生存期内以增量方式保存状态。请将以下代码粘贴到 App.xaml.cs 中现有 Closing 处理程序的上方。

    // Code to execute when the application is closing (for example, the user pressed the Back button)
    // This code will not execute when the application is deactivated.
    private void Application_Closing(object sender, ClosingEventArgs e)
    {
      // The application will not be tombstoned, so save only to isolated storage.
      if (!string.IsNullOrEmpty(ApplicationDataObject))
      {
        SaveDataToIsolatedStorage("myDataFile.txt", ApplicationDataObject);
      }
    }
    
  9. 现在创建一些用于获取全局应用程序数据的帮助器方法。将以下代码置于应用程序类中,您便无需在应用程序的每个页面中都进行实现。首先,GetDataAsync 方法定义为公共方法,这样它就可以从应用程序页面中调用。该方法可创建新线程,并调用 GetData 帮助器方法。您应当始终异步执行耗时的操作,以便应用程序的用户界面可持续保持响应。

    此示例使用在独立存储中保存的时间戳来记录应用程序数据的上次保存时间。GetData 帮助器方法可检查此时间戳,如果数据的保存时间少于 30 秒,该帮助器方法会加载独立存储中的应用程序数据,并将其存储在 ApplicationDataObject 属性中。先前创建了 ApplicationDataObjectChanged 事件,以便正在进行侦听的任何页面都会在 ApplicationDataObject 发生更改时接收到一个事件。此外,ApplicationDataStatus 字段也进行了更新,以便页面能够显示数据是从独立存储检索的。

    如果时间戳指示独立存储中的数据保存时间超过 30 秒,则应用程序将从 Web 检索最新数据。在此示例中,HttpWebRequest 用于启动请求。帮助器方法 HandleWebResponse 显示在下一步骤中。

    public void GetDataAsync()
    {
      // Call the GetData method on a new thread.
      Thread t = new Thread(new ThreadStart(GetData));
      t.Start();
    }
    
    private void GetData()
    {
      // Check the time elapsed since data was last saved to isolated storage.
      TimeSpan TimeSinceLastSave = TimeSpan.FromSeconds(0);
      if (IsolatedStorageSettings.ApplicationSettings.Contains("DataLastSavedTime"))
      {
        DateTime dataLastSaveTime = (DateTime)IsolatedStorageSettings.ApplicationSettings["DataLastSavedTime"];
        TimeSinceLastSave = DateTime.Now - dataLastSaveTime;
      }
    
      // Check to see if data exists in isolated storage and see if the data is fresh.
      // This example uses 30 seconds as the valid time window to make it easy to test. 
      // Real apps will use a larger window.
      IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
      if (isoStore.FileExists("myDataFile.txt") && TimeSinceLastSave.TotalSeconds < 30)
      {
        // This method loads the data from isolated storage, if it is available.
        StreamReader sr = new StreamReader(isoStore.OpenFile("myDataFile.txt", FileMode.Open));
        string data = sr.ReadToEnd();
        sr.Close();
    
        ApplicationDataStatus = "data from isolated storage";
        ApplicationDataObject = data;
      }
      else
      {
        // Otherwise, it gets the data from the web. 
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("http://windowsteamblog.com/windows_phone/b/windowsphone/rss.aspx"));
        request.BeginGetResponse(HandleWebResponse, request);
      }
    }
    
    
  10. HandleWebResponse 帮助器方法用于处理在 GetData 中启动的 Web 请求的结果。此方法尝试读取响应流中的数据。如果读取成功,它会将数据存储在 ApplicationDataObject 变量中,从而对任何已注册该变量的页面引发 ApplicationDataObjectChanged 事件。它还会设置状态变量,以指示数据是从 Web 检索的。如果 Web 请求不成功,状态也会进行更新。

    private void HandleWebResponse(IAsyncResult result)
    {
      // Put this in a try block in case the web request was unsuccessful.
      try
      {
        // Get the request from the IAsyncResult.
        HttpWebRequest request = (HttpWebRequest)(result.AsyncState);
    
        // Read the response stream from the response.
        StreamReader sr = new StreamReader(request.EndGetResponse(result).GetResponseStream());
        string data = sr.ReadToEnd();
    
        // Use the Dispatcher to call SetData on the UI thread, passing the retrieved data.
        //Dispatcher.BeginInvoke(() => { SetData(data, "web"); });
        ApplicationDataStatus = "data from web.";
        ApplicationDataObject = data;
      }
      catch
      {
        // If the data request fails, alert the user.
        ApplicationDataStatus = "Unable to get data from Web.";
        ApplicationDataObject = “”;
      }
    }
    
    
  11. 在 App.xaml.cs 中实现的最后一个帮助器方法是 SaveDataToIsolatedStorage。此方法从 DeactivatedClosing 事件处理程序中调用。它仅将提供的值保存到指定的文件,并重置在 GetData 中检查的时间戳。

    private void SaveDataToIsolatedStorage(string isoFileName, string value)
    {
      IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
      StreamWriter sw = new StreamWriter(isoStore.OpenFile(isoFileName, FileMode.OpenOrCreate));
      sw.Write(value);
      sw.Close();
      IsolatedStorageSettings.ApplicationSettings["DataLastSaveTime"] = DateTime.Now;
    }
    
    

此部分将指导您对主页面类进行必要的更改以实现示例应用程序。

修改页面类

  1. 在对应用程序类进行修改以管理应用程序状态之后,每个页面中只需要少量代码即可使用状态数据。首先,创建两个 TextBlock 控件。其中一个将用于显示应用程序数据,另一个将用于显示状态变量,以指示数据是从何处检索的。实际的应用程序将会以更好用或更有趣的方式提供数据,但是为了简便起见,此示例仅在页面上将数据显示为文本。

    在 MainPage.xaml 文件中,将以下 XAML 代码放置在名为“ContentPanel”的 Grid 元素中。

    <TextBlock Height="30" HorizontalAlignment="Left" Margin="20,20,0,0" Name="statusTextBlock" Text="no status" VerticalAlignment="Top" Width="424" />
    <TextBlock HorizontalAlignment="Left" Margin="20,60,0,0" Name="dataTextBlock" Text="no data" VerticalAlignment="Top" Width="424" Foreground="{StaticResource PhoneAccentBrush}" MaxWidth="424" />
    
  2. 接下来,添加一个名为 _isNewPageInstance 的布尔变量,用于稍后确定页面是否为新实例。请将以下代码行添加到 MainPage.xaml.cs 的 MainPage 类定义中。

    public partial class MainPage : PhoneApplicationPage
    {
            bool _isNewPageInstance = false;
    
    
  3. MainPage 类的构造函数中,将 _isNewPageInstance 设置为 true。如果用户只是向后导航到现有页面,将不会调用该构造函数。在这种情况下,无需重新加载状态。接下来,注册 App.xaml.cs 中定义的 ApplicationDataObjectChanged 事件处理程序。这将使页面知道应用程序数据进行修改的时间。

    // Constructor
    public MainPage()
    {
      InitializeComponent();
    
      _isNewPageInstance = true;
    
      // Set the event handler for when the application data object changes.
      (Application.Current as ExecutionModelApplication.App).ApplicationDataObjectChanged +=
                    new EventHandler(MainPage_ApplicationDataObjectChanged);
    }
    
    
  4. 每当用户导航到某个页面时,系统都会调用 OnNavigatedTo(NavigationEventArgs) 方法。请检查 _isNewPageInstance 的值,以查看页面是新页面,还是用户导航回内存中已有的页面。如果页面是新页面,且 ApplicationDataObject 变量不为 null,请调用稍后将定义的 UpdateApplicationUI 帮助器方法,以更新页面 UI 中的 TextBox 控件。如果 ApplicationDataObject 变量为 null,则需要检索数据,无论是从独立存储中检索还是从 Web 中检索。在这种情况下,状态 TextBlock 会进行更新以使用户知道该数据正在检索中,并且将调用 App.xaml.cs 中定义的 GetDataAsync 帮助器方法。最后,_isNewPageInstance 将设置为 false。

    请将以下方法定义粘贴到 MainPage.xaml.cs 中。

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
      // If _isNewPageInstance is true, the page constructor has been called, so
      // state may need to be restored.
      if (_isNewPageInstance)
      {
        // If the application member variable is not empty,
        // set the page's data object from the application member variable.
        if ((Application.Current as ExecutionModelApplication.App).ApplicationDataObject != null)
        {
          UpdateApplicationDataUI();
        }
        else
        {
          // Otherwise, call the method that loads data.
          statusTextBlock.Text = "getting data...";
          (Application.Current as ExecutionModelApplication.App).GetDataAsync();
        }
      }
    
      // Set _isNewPageInstance to false. If the user navigates back to this page
      // and it has remained in memory, this value will continue to be false.
      _isNewPageInstance = false;
    }
    
    
  5. 最后,创建 ApplicationDataObjectChanged 事件的事件处理程序。该方法仅调用 UpdateApplicationDataUI 来用新数据更新页面 UI。

    // The event handler called when the ApplicationDataObject changes.
    void MainPage_ApplicationDataObjectChanged(object sender, EventArgs e)
    {
      // Call UpdateApplicationData on the UI thread.
      Dispatcher.BeginInvoke(() => UpdateApplicationDataUI());      
    }
    void UpdateApplicationDataUI()
    {
      // Set the ApplicationData and ApplicationDataStatus members of the ViewModel
      // class to update the UI.
      dataTextBlock.Text = (Application.Current as ExecutionModelApplication.App).ApplicationDataObject;
      statusTextBlock.Text = (Application.Current as ExecutionModelApplication.App).ApplicationDataStatus;
    }
    
    

在 Windows Phone 7.5 中,只要有足够的内存可供前台应用程序顺畅运行,应用程序就会在用户导航离开时置于休眠状态。当应用程序休眠后还原时,UI 状态会自动保留。若要验证页面状态在逻辑删除后是否正确还原,您需要启用调试器中的自动逻辑删除功能。

通过从“项目”菜单中选择“[应用程序名称] 属性…”,或者通过右键单击“解决方案资源管理器”中的项目,然后选择“属性”,打开“项目属性”。在“调试”标签中,选中标有“在调试期间取消激活时逻辑删除”的复选框。

启用逻辑删除之后,按 F5 可开始调试应用程序。当应用程序初次加载时,独立存储或应用程序的 State 字典中没有任何状态数据,因此将会显示数据,并且状态字段将指示该数据是从 Web 检索的。如果按“开始”按键取消激活应用程序,然后按“返回”按键重新激活它,则将从应用程序的 State 字典中检索数据。现在,再次按“返回”按键以终止应用程序,然后再次启动应用程序;如果您在 30 秒内启动该应用程序,它应该从独立存储中检索数据。

注意注意:

对于 XNA Framework 应用程序,“在调试期间取消激活时逻辑删除”复选框位于“项目属性”对话框的“XNA Game Studio”标签中。

显示: