Compartir a través de


How to preserve and restore app state for Windows Phone 8

[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]

 

The Windows Phone execution model allows only one app to run in the foreground at a time. When the user navigates away from an app, the app is typically put in a dormant state. In a dormant state, the app’s code no longer executes, but the app remains in memory. When the user presses the Back button to return to a dormant app, it resumes running and its state is automatically restored. It is possible, however, for an app to be tombstoned after the user navigates away. If the user navigates back to a tombstoned app, the app must restore its own state because it is no longer in memory. The PhoneApplicationService class provides four events that help you to preserve and maintain app state: Launching, Activated, Deactivated, and Closing. These events provide an opportunity for your app to restore any global app data used by multiple app pages. Examples of this type of data include authentication keys or the results of a web service query. This topic demonstrates a pattern for using these events to save and restore app state.

This topic contains the following sections.

 

Storing app state data

It is common for a phone app to use data it obtains from a network resource, such as a web service. It is often the case that multiple pages within the app use this data from the network. This data can be considered to be part of the state of the app. When your app is deactivated and reactivated, this data is lost unless your app has stored it on the device. Your app can simply make another query to the network resource to get the data again, but there are two ways that you can store state data when your app is being deactivated and restore it when your app is reactivated. The first way is to use persistent storage. This includes isolated storage and a local database. The State dictionary of the PhoneApplicationService is a temporary storage location that persists only as long as your app is tombstoned, but is much quicker to access than persistent storage. Using these two storage types correctly will have a big impact on the user experience and load time of your app.

Important Note:

Any data that you store in the State dictionary must be serializable, either directly or by using data contracts. For more information, see Using Data Contracts.

Structure of this example

This topic will walk you through the creation of an app that displays data from the web. As the app is tombstoned and terminated, this data will be preserved, restored, and retrieved as the app is launched, deactivated, tombstoned, and reactivated. All of the time-intensive data operations will be done asynchronously so that the app’s user interface continues to be responsive. The app state data used in this example is a simple string of data obtained from a website and displayed in a TextBlock on the page. A real app will typically use more structured data and a more complex user interface, but the basic concept remains the same.

This topic walks you through an implementation of app state management in two parts. First, the main Application class will be modified to handle the app state events. Next, the implementation of the PhoneApplicationPage object that uses the preserved app state will be described.

Modifying the Application class

This section walks you through the changes that should be made to the main application class to implement the sample app.

To modify the Application class

  1. In Visual Studio, create a new **Windows Phone App ** project. This template is in the Windows Phone category.

  2. All of the steps in this section will modify the App.xaml.cs file that included in all Windows Phone project templates. First, add the following using directives to the top of the file.

    using System.Threading;
    using System.IO;
    using System.IO.IsolatedStorage;
    
  3. The following code creates a public property, ApplicationDataObject, which will be used to access the app data, which in this example is a simple string object. The property uses a private variable, _applicationDataObject, to store the data. The ApplicationDataObjectChanged event and OnApplicationDataObjectChanged are created to allow pages within the app to receive an event whenever the app data changes and redraw their state. Paste the following code inside the App class definition in App.xaml.cs.

    // 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. In this example, the ApplicationDataObject property is used to expose app data to pages. A separate property, ApplicationDataStatus, is used to expose the source of the app data – whether it came from the web, isolated storage, or the app’s State dictionary. Paste the following property definition below the code from the previous step.

    // Declare a public property to store the status of the application data.
    public string ApplicationDataStatus { get; set; }
    
  5. Now, implement the app event handlers. Stubs for these event handlers are provided in App.xaml.cs in all Windows Phone project templates. The Launching event is raised when the user initially launches the app, such as by clicking the icon for the app in the phone’s apps list. This example leaves this event handler empty and handles the loading of app state in a different place in the code. Any code executed in this event handler will delay the app’s initial startup. For this reason, you should never attempt to access any time-consuming resources in this event handler. This includes accessing isolated storage, accessing database storage, or attempting to access a web service. All of the app events have a time limit of 10 seconds to complete. If your app exceeds this limit for any event, it will be terminated immediately.

    // 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. The Activated event is raised when the app is dormant or tombstoned and the user navigates back to the app. Like the Launching event, code executed in this event will delay the app resuming. Do not access isolated storage, access database storage, or execute synchronous web requests in this handler. The IsApplicationInstancePreserved property of ActivatedEventArgs lets your app know if the app is returning from a dormant state or if it was tombstoned. This is mainly of use to XNA Framework apps because they do not receive the page-level events. In this example, if ApplicationInstancePreserved is true, meaning that the app was dormant and therefore its state was automatically preserved, the ApplicationDataStatus property is updated and the handler exits. Next, the handler checks to see if the app data is available in the State dictionary. If it is available, meaning that the app was tombstoned and saved state here during the Deactivated event, the ApplicationDataObject is populated from the State dictionary and the data status property is updated. Replace the existing Application_Activated handler with the following code.

    // 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. The Deactivated event is called whenever the user navigates forward away from the app. Although apps are typically made dormant after they are deactivated, there is no way to know at this point whether the app will be tombstoned or terminated after this event. For this reason, you should save app state in the State dictionary and to isolated storage or database storage. This example app saves the ApplicationDataObject to State and to IsolatedStorage. The method SaveDataToIsolatedStorage is a helper method that is defined later in this topic. Like all app events, your app will be terminated if it takes longer than 10 seconds to complete this handler. For this reason, we recommend that you save your state incrementally throughout the lifetime of your app. This event is merely a final opportunity to save any unsaved data. Paste the following code over the existing Deactivated handler in App.xaml.cs.

    // 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. The Closing event is raised when the user uses the Back button to navigate backwards past the first page of your app. After this event, your app is terminated. For the user to return to your app, they must relaunch it. For this reason, any state data should be saved to isolated storage, but there is no need to save it to the State dictionary. Once again, if your app takes more than 10 seconds to complete this event, it will be terminated immediately, so it is a good idea to save state incrementally throughout the lifetime of the app. Paste the following code over the existing Closing handler in App.xaml.cs.

    // 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. Now create some helper methods to acquire the global app data. Putting this code into the app class prevents you from having to implement it in each page of the app. First, the GetDataAsync method is defined as public so that it can be called from the app pages. It creates a new thread and calls the GetData helper method. You should always perform time-intensive operations asynchronously so that the user interface of your app remains responsive.

    This example uses a timestamp, saved in isolated storage, to record the time at which the app data was last saved. The GetData helper method checks this time stamp and, if the data was saved less than 30 seconds ago, loads the app data from isolated storage and stores it in the ApplicationDataObject property. Previously, the ApplicationDataObjectChanged event was created so that any page that is listening will receive an event when the ApplicationDataObject is changed. Also, the ApplicationDataStatus field is updated so that the page can show that the data was retrieved from isolated storage.

    If the time stamp indicates that the data in isolated storage is older than 30 seconds, the app will retrieve fresh data from the web. In this example, HttpWebRequest is used to initiate the request. The helper method HandleWebResponse is shown in the next step.

    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("https://windowsteamblog.com/windows_phone/b/windowsphone/rss.aspx"));
        request.BeginGetResponse(HandleWebResponse, request);
      }
    }
    
  10. The HandleWebResponse helper method is used to process the result of the web request that was initiated in GetData. This method attempts to read the data from the response stream. If it is successful, it stores the data in the ApplicationDataObject variable, which raises the ApplicationDataObjectChanged event for any pages that have registered for it. It also sets the status variable to indicate that the data was retrieved from the web. If the web request was unsuccessful, the status is also updated.

    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. The final helper method to implement in App.xaml.cs is SaveDataToIsolatedStorage. This method is called from the Deactivated and Closing event handlers. It simply saves the provided value to the specified file and resets the time stamp that is checked in 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;
    }
    

Modifying the Page class

This section walks you through the changes that should be made to the main page class to implement the sample app.

To modify the Page class

  1. Once the app class has been modified to manage app state, only a small amount of code is required in each page to use the state data. First, create two TextBlock controls. One will be used to display the app data, and the other will be used to show the status variable that indicates from where the data was retrieved. A real app would present the data in a more usable or interesting manner, but in the interest of simplicity, this example just displays the data on the page as text.

    In the MainPage.xaml file, place the following XAML code in the Grid element named “ContentPanel”.

    <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. Next, add a Boolean variable called _isNewPageInstance that will be used later to determine if the page is a new instance. Add the following line to the MainPage class definition in MainPage.xaml.cs.

    public partial class MainPage : PhoneApplicationPage
    {
            bool _isNewPageInstance = false;
    
  3. In the constructor for the MainPage class, set _isNewPageInstance to true. The constructor will not be called if the user is simply navigating backward to an existing page. In this case, you do not need to reload state. Next, register for the ApplicationDataObjectChanged event handler that was defined in App.xaml.cs. This will let the page know when the app data has been modified.

    // 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. The OnNavigatedTo(NavigationEventArgs) method is called whenever the user navigates to a page. Check the value of _isNewPageInstance to see if the page is new or if the user is navigating back to a page that is already in memory. If the page is new, and the ApplicationDataObject variable is not null, call the UpdateApplicationUI helper method that will be defined later to update the TextBox controls in the page’s UI. If the ApplicationDataObject variable is null, then the data needs to be retrieved, either from isolated storage or from the web. In this case, the status TextBlock is updated to let the user know that data is being retrieved and the GetDataAsync helper method defined in App.xaml.cs is called. Finally, _isNewPageInstance is set to false.

    Paste the following method definition into 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. Finally, create the event handler for the ApplicationDataObjectChanged event. This method simply calls UpdateApplicationDataUI to update the page’s UI with the new data.

    // 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;
    }
    

Debugging app state

On Windows Phone, apps are made dormant when the user navigates away, as long as sufficient memory is available for the foreground app to run smoothly. When an app is dormant and then restored, the UI state is automatically preserved. To verify that your page state is restored properly after tombstoning, you need to enable automatic tombstoning in the debugger.

Open the Project Properties dialog by selecting [Application Name] Properties… from the Project menu or by right-clicking the project in Solution Explorer and selecting Properties. In the Debug tab, select the check box labeled Tombstone upon deactivation while debugging.

After enabling tombstoning, press F5 to begin debugging the app. When the app first loads, there is no state data in isolated storage or in the app’s State dictionary, so the data will be displayed and the status field will indicate that the data was retrieved from the web. If you press the Start button to deactivate the app and then press the Back button to reactivate it, the data will be retrieved from the app’s State dictionary. Now, press the Back button again to terminate the app and then launch the app again; if you launch the app within 30 seconds, it should retrieve the data from isolated storage.

Note

For XNA Framework apps, the Tombstone upon deactivation while debugging check box is on the XNA Game Studio tab in the Project Properties dialog.