Condividi tramite


Handling Activation and Deactivation

patterns & practices Developer Center

On this page: Download:
You Will Learn | Overview of the Solution | Inside the Implementation - Reactivation and the Pivot Control Download code samples
Download book as PDF

You Will Learn

  • How to robustly persist the UI state when an application is deactivated.
  • How to robustly restore the UI state when an application is reactivated.
  • How to robustly restore the UI state to a Pivot control when an application is reactivated.

Windows Phone applications must be able to restore the UI state if the application is reactivated after the user has taken a call or used another application. In this scenario, the operating system makes the application dormant, tombstoned, and possibly terminates it, but it gives you the opportunity to save any data that you can use to put the application back in the same state if and when the operating system reactivates it.

The application must restore its state if the user returns to the application after taking a call or using another application.

For more information about activation, deactivation, and tombstoning, see, "Execution Model Overview for Windows Phone" on MSDN.

The Windows Phone platform provides application-level activation and deactivation events, with the Application_Activated event handler in the App class notifying the application instance about whether it has resumed from dormancy or a tombstoned state.

The mobile client application is implemented using the MVVM pattern, with each view model being responsible for restoring its state when returning from a tombstoned state. When the mobile client application is reactivated, the information that indicates whether the application is returning from a tombstoned state is stored in the ApplicationFrameNavigationService class. The ApplicationFrameNavigationService class also provides functionality to determine if a page needs to recover from tombstoning, by using the frame.BackStack property. The base view model class then uses the ApplicationFrameNavigationService class to ensure that only pages that were tombstoned are resumed. In addition, the base view model class also provides two methods that can be overridden by child view models to implement logic that captures the UI state when deactivation occurs, and restores the UI state.

Overview of the Solution

The Windows Phone platform provides much of the infrastructure that you need to enable your application to restore the state of the UI when the application is reactivated:

  • The phone uses a navigation service to facilitate navigating between pages. The PhoneApplicationFrame class exposes a BackStack property that can be used to build a dictionary which tracks the pages that were tombstoned.
  • The phone uses a navigating event to notify the application when navigation is requested, and a navigated event to notify the application when the content that is being navigated to has been found and is available.
  • The phone uses the application Activated event to notify the application when it has returned from deactivation. This event also informs the application about whether the instance was preserved, as is the case when returning from dormancy.
  • The phone automatically records which screen your application is displaying, along with the current navigation stack, when it deactivates the application; then it rebuilds the navigation stack and redisplays this screen if the phone reactivates the application.
  • The phone provides application-level and page-level state dictionaries in which you can store key/value pairs. These dictionaries are preserved when an application is tombstoned. When an application is activated after being tombstoned, these dictionaries are used to restore application and page state.

It's the application's responsibility to determine what state data it needs to save to be able to restore the application to the same state when the phone reactivates it.

Hh821027.note(en-us,PandP.10).gifChristine Says:
Christine
                The application should store only enough data to be able to restore the application to the same state it was in when it was deactivated. Also, remember it's possible that a deactivated application will never be reactivated, so you must also save any important data to permanent storage.</td>

In the Tailspin mobile client application, when the user navigates away from a page that gets recreated every time the page is visited, such as the AppSettingsView, no state is stored for that page. However, if the user navigates away from the page unintentionally, then the data required to recreate the page at a later time is stored in the application-level state dictionary, which survives tombstoning. For pages that do not get recreated every time they are visited, such as the SurveyListView, the data required to recreate the page is always stored in the application-level state dictionary irrespective of whether the navigation is intentional or unintentional. When the operating system reactivates the application, the operating system will redisplay the view that was active when the application was deactivated. When returning from dormancy, all object instances will still be in memory in the exact state prior to dormancy, so the application does not need to perform any additional tasks. However. when returning from a tombstoned state, the view model locator in the Tailspin mobile client application will instantiate the view model for the view, and the view model will restore its saved state and initialize any services so that it is back in the state it was in when it was originally deactivated.

To meet the Windows Phone certification requirements, your application must complete the deactivation and reactivation processes within 10 seconds.

Inside the Implementation

Now is a good time to walk through the code that handles the activation, navigated and navigating events in more detail. As you go through this section, you may want to download the Windows Phone Tailspin Surveys application from the Microsoft Download Center.

The Application_Activated event handler in the App class notifies the application about whether it has resumed from dormancy or a tombstoned state. The following code example shows the Application_Activated event handler.

private void Application_Activated(object sender, ActivatedEventArgs e)
{
  if (e.IsApplicationInstancePreserved)
  {
    PhoneApplicationService.Current.State.Clear();
  }
  else
  {
    ViewModelLocator.NavigationService.RecoveredFromTombstoning = true;
  }
}

The IsApplicationInstancePreserved property indicates whether the application instance was preserved intact in memory. If the IsApplicationInstancePreserved property of the ActivatedEventArgs is true, it means that the application has returned from dormancy and thus the application state dictionary is cleared. If the IsApplicationInstancePreserved property of the ActivatedEventArgs is false, it means that the application has returned from a tombstoned state and thus the RecoveredFromTombstoning property of the ApplicationFrameNavigationService instance is set to true.

The ApplicationFrameNavigationService class provides the DoesPageNeedtoRecoverFromTombstoning method, which determines if a page needs to recover from tombstoning. The following code example shows this method and the UpdateTombstonedPageTracking method.

private Dictionary<string, bool> tombstonedPages;
...

public bool DoesPageNeedtoRecoverFromTombstoning(Uri pageUri)
{
  if (!RecoveredFromTombstoning) return false;

  if (tombstonedPages == null)
  {
    tombstonedPages = new Dictionary<string, bool>();
    tombstonedPages.Add(pageUri.ToString(), true);
    foreach (var journalEntry in frame.BackStack)
    {
      tombstonedPages.Add(journalEntry.Source.ToString(), true);
    }
    return true;
  }

  if (tombstonedPages.ContainsKey(pageUri.ToString()))
  {
    return tombstonedPages[pageUri.ToString()];
  }
  return false;
}

public void UpdateTombstonedPageTracking(Uri pageUri)
{
  tombstonedPages[pageUri.ToString()] = false;
}

The DoesPageNeedtoRecoverFromTombstoning method is used by the ViewModel class in order to determine whether or not to call the OnPageResumeFromTombstoning method in the ViewModel class. The method makes the assumption that the list of pages that were tombstoned can be determined by taking the first page that was instantiated due to a tombstoning recovery, and then examining the backstack. It uses the BackStack property of the PhoneApplicationFrame class to populate a dictionary with the pages that were tombstoned and uses this dictionary to track whether the ViewModel class called the OnPageResumedFromTombstoning method for each tombstoned page. The UpdateTombstonedPageTracking method simply updates a given page entry in the tombstonedPages dictionary to mark that the page has completed tombstone recovery.

The developers at Tailspin created the IPhoneApplicationServiceFacade interface which is implemented by the PhoneApplicationServiceFacade class. This facade provides a simplified interface to the PhoneApplicationService class. The following code example shows the IPhoneApplicationServiceFacade interface.

A facade is a design pattern that provides a simplified interface for a class. The facade translates calls to its interface into calls to the original class. This approach enables writing loosely coupled code that is testable.

public interface IPhoneApplicationServiceFacade
{
  void Save(string key, object value);
  T Load<T>(string key);
  void Remove(string key);
}

The PhoneApplicationServiceFacade class has methods that save and load any state that the page needs when it's navigated to or navigated away from during application deactivation. The following code example shows the complete PhoneApplicationServiceFacade class.

A service is a facade that exposes a loosely coupled unit of functionality that implements one action.

Hh821027.note(en-us,PandP.10).gifMarkus Says:
Markus When an application is no longer in the foreground, it is said to be dormant.We often refer to the process of terminating a dormant application as "tombstoning."

Note

There is also a PhoneApplicationPage class that allows you to store transient page state as you navigate away from a page and restore the state when you return to the page.

public class PhoneApplicationServiceFacade : IPhoneApplicationServiceFacade
{
  public void Save(string key, object value)
  {
    if (PhoneApplicationService.Current.State.ContainsKey(key))
    {
      PhoneApplicationService.Current.State.Remove(key);
    }

    PhoneApplicationService.Current.State.Add(key, value);
  }

  public T Load<T>(string key)
  {
    object result;

    if (!PhoneApplicationService.Current.State.TryGetValue(key, out result))
    {
      result = default(T);
    }
    else
    {
      PhoneApplicationService.Current.State.Remove(key);
    }

    return (T)result;
  }

  public void Remove(string key)
  {  
    if (PhoneApplicationService.Current.State.ContainsKey(key))
    {
      PhoneApplicationService.Current.State.Remove(key);
    }
  }
}
Hh821027.note(en-us,PandP.10).gifMarkus Says:
Markus The objects that you save in the State dictionary must be serializable.

Each view model is responsible for managing its own state when the page is navigated to or navigated away from, either intentionally or during application deactivation. Most of the view models in the application derive from the ViewModel class that listens to the Navigated and Navigating events in the ApplicationFrameNavigationService class, which is a facade over the PhoneApplicationFrame SDK class. The base ViewModel class also uses the ApplicationFrameNavigationService class to determine if the application is returning from a tombstoned state. The following code example shows part of the abstract ViewModel class.

Note

The developers at Tailspin chose to make each view model responsible for persisting and reloading its own state.

public abstract class ViewModel : NotificationObject, IDisposable
{
  private readonly INavigationService navigationService;
  private readonly IPhoneApplicationServiceFacade phoneApplicationServiceFacade;
  private bool disposed;
  private readonly Uri pageUri;
  private static Uri currentPageUri;

  protected ViewModel(INavigationService navigationService,
    IPhoneApplicationServiceFacade phoneApplicationServiceFacade,
    Uri pageUri)
  {
    this.pageUri = pageUri;
    this.navigationService = navigationService;
    this.phoneApplicationServiceFacade = phoneApplicationServiceFacade;

    this.navigationService.Navigated +=
      this.OnNavigationService_Navigated;
    this.navigationService.Navigating += 
      this.OnNavigationService_Navigating;
  }

  void OnNavigationService_Navigating(object sender, 
    System.Windows.Navigation.NavigatingCancelEventArgs e)
  {
    if (currentPageUri == null || pageUri == null) return;

    if (currentPageUri.ToString().StartsWith(pageUri.ToString()))
    {
      OnPageDeactivation(e.IsNavigationInitiator);
    }
  }

  void OnNavigationService_Navigated(object sender, 
    System.Windows.Navigation.NavigationEventArgs e)
  {
    if (IsResumingFromTombstoning)
    {
        if (e.Uri.ToString().StartsWith(pageUri.ToString()))
        {
            OnPageResumeFromTombstoning();
            navigationService.UpdateTombstonedPageTracking(pageUri);
        }
    }   
    currentPageUri = e.Uri;
  }

  ...

  protected bool IsResumingFromTombstoning
  {
    get
    {
      return navigationService.DoesPageNeedToRecoverFromTombstoning(pageUri);
    }
  }

  ...

  public IPhoneApplicationServiceFacade
    PhoneApplicationServiceFacade
  {
    get { return this.phoneApplicationServiceFacade; }
  }

  public virtual void OnPageDeactivation(bool isIntentionalNavigation)
  {
  }

  public abstract void OnPageResumeFromTombstoning();

  ...

  protected virtual void Dispose(bool disposing)
  {
    ...
    if (disposing)
    {
       navigationService.Navigated -=
         this.OnNavigationService_Navigated;
       navigationService.Navigating -=
         this.OnNavigationService_Navigating;
    }
    ...
  }
}

The OnNavigationService_Navigating event handler calls the OnPageDeactivation method only on the view model of the page where navigation is requested. The IsNavigationInitiator property of the NavigatingCancelEventArgs class is passed as a parameter into the OnPageDeactivation method. This property indicates whether the current application is the both the origin and destination of the navigation. If it is, it means that page-based navigation within the application is occurring. In this case, deactivation will not be performed. If the current application is not the origin and destination of the navigation (for instance the Windows Phone device has received a phone call), deactivation will occur and the state of the page will be stored in the application state. However, the SelectedPivotIndex property of the SurveyListViewModel class is stored even when the user intentionally navigates away from the page. This is because the mobile client application may be deactivated from another page, such as the FilterSettingsView page, and when the application is reactivated, that page will be restored. However, when the user navigates back to the SurveyListView page, the SelectedPivotIndex property must be restored so that the user sees the survey list that was being viewed before the application was deactivated.

The OnNavigationService_Navigated event handler is used to determine if the view model instance needs to recover from tombstoning. It first checks the IsResumingFromTombstoning property to determine whether the application is returning from a tombstoned state, and whether the view model has already recovered its tombstoned state. If the IsResumingFromTombstoning property is true, the application is returning from tombstoning. If the IsResumingFromTombstoning property is false, the application is either returning from dormancy or undertaking a page-based navigation. If the application is returning from tombstoning, the OnPageResumeFromTombstoning method is called on the view model of the page that's been navigated to, to restore the desired state to the view model. The ApplicationFrameNavigationService is then notified that the page has completed tombstone recovery, thus ensuring that pages that were tombstoned are resumed only once.

Hh821027.note(en-us,PandP.10).gifJana Says:
Jana Remember, the navigating event notifies the application when navigation is requested, and the navigated event notifies the application when the content that is being navigated to has been found and is available.

Each view model can override the OnPageResumeFromTombstoning and OnPageDeactivation methods to provide its custom state saving and restoring behavior. The following code example shows how the SurveyListViewModel class saves and restores its state.

public SurveyListViewModel(
  ISurveyStoreLocator surveyStoreLocator,
  ISurveysSynchronizationService synchronizationService,
  INavigationService navigationService,
  IPhoneApplicationServiceFacade phoneApplicationServiceFacade,
  IShellTile shellTile,
  ISettingsStore settingsStore,
  ILocationService locationService)
  : base(navigationService, phoneApplicationServiceFacade,
         new Uri(@"/Views/SurveyList/SurveyListView.xaml", UriKind.Relative))
{
  ...
}

...

public override void OnPageDeactivation(bool isIntentionalNavigation)
{
  this.PhoneApplicationServiceFacade.Save("MainPivot", 
    this.SelectedPivotIndex);
}

public override sealed void OnPageResumeFromTombstoning()
{
  this.selectedPivotIndex = 
    this.PhoneApplicationServiceFacade.Load<int>("MainPivot");
}

The OnPageDeactivation method is called by the Navigating event of the PhoneApplicationFrame class when the page is intentionally or unintentionally navigated away from. It uses the Save method from the PhoneApplicationServiceFacade class to save the MainPivot key/value pair to application state, with the value being the SelectedPivotIndex property value.

The OnPageResumeFromTombstoning method is called by the Navigated event of the PhoneApplicationFrame class when the page is resuming from tombstoning. It uses the Load method from the PhoneApplicationServiceFacade class to load the MainPivot key/value pair from the application state and store the value in the selectedPivotIndex field.

Hh821027.note(en-us,PandP.10).gifChristine Says:
Christine If the application has been deactivated, and the user relaunches it from Start, the state data is discarded.

Reactivation and the Pivot Control

When your application is reactivated by the operating system, you should restore the UI state, which in the Tailspin application includes displaying the active question if the application was made dormant or tombstoned while the user was completing a survey. The following code example shows the OnPageResumeFromTombstoning method of the TakeSurveyViewModel class, which restores the SelectedPivotIndex property, along with the survey answer and survey id, from the application state, when the application returns from a tombstoned state.

public override sealed void OnPageResumeFromTombstoning()
{
  this.tombstoned = this.PhoneApplicationServiceFacade
    .Load<SurveyAnswer>("TakeSurveyAnswer");
  this.SelectedPivotIndex = this.PhoneApplicationServiceFacade
    .Load<int>("TakeSurveyCurrentIndex");
  this.surveyId = this.PhoneApplicationServiceFacade
    .Load<string>("TakeSurveyId");
  Initialize(this.surveyId);
  this.locationService.StartWatcher();
}

This method is called by the Navigated event of the PhoneApplicationFrame class when the page is resuming from tombstoning. It uses the Load method from the PhoneApplicationServiceFacade class to load from application state the TakeSurveyAnswer key/value pair, the TakeSurveyCurrentIndex key/value pair, and the TakeSurveyId key/value pair. It then calls the Initialize method, passing the surveyId field into the method.

To display the correct question when the application is reactivated, Tailspin changes the SelectedItem property of the control. The following code example shows the event handlers in the code-behind for the TakeSurveyView page.

private bool loaded;

private void PivotSelectionChanged(object sender, 
  System.Windows.Controls.SelectionChangedEventArgs e)
{
  if (this.loaded)
  {
    ((TakeSurveyViewModel)this.DataContext)
      .SelectedPivotIndex = this.questionsPivot.SelectedIndex;
  }
}

private void ControlLoaded(object sender, 
  System.Windows.RoutedEventArgs e)
{
  var vm = (TakeSurveyViewModel)this.DataContext;
  this.questionsPivot.SelectedItem = 
    this.questionsPivot.Items[vm.SelectedPivotIndex];
  this.loaded = true;
  ...
}

The SelectedPivotIndex of the TakeSurveyViewModel class tracks the currently active question in the Pivot control.

Next Topic | Previous Topic | Home

Last built: May 25, 2012