다음을 통해 공유


Handling suspend, resume, and activation in a Windows Store business app using C#, XAML, and Prism

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

From: Developing a Windows Store business app using C#, XAML, and Prism for the Windows Runtime

Previous page | Next page

Learn how to use Prism for the Windows Runtime to manage the execution states of Windows Store business apps that use the Model-View-ViewModel (MVVM) pattern. The AdventureWorks Shopper reference implementation saves view and view model state when the app is suspended, and restores that state when the app is reactivated from termination.

Download

After you download the code, see Getting started using Prism for the Windows Runtime for instructions on how to compile and run the reference implementation, as well as understand the Microsoft Visual Studio solution structure.

You will learn

  • How Windows determines an app's execution state.
  • How to implement support for suspend, resume, and activation by using the Microsoft.Practices.Prism.StoreApps library.
  • How to save and restore view model state.
  • How to save and restore view state.
  • How to save and restore state from service and repository classes.
  • How to close a Windows Store app.

Applies to

  • Windows Runtime for Windows 8.1
  • C#
  • Extensible Application Markup Language (XAML)

Making key decisions

Windows Store apps should be designed to save their state and suspend when the user switches away from them. They could restore their state and resume when the user switches back to them. The following list summarizes the decisions to make when implementing suspend and resume in your app:

  • Should your app be activated through any contracts or extensions or will it only be activated by the user launching it?
  • Does your app need to behave differently when it's closed by the user rather than when it's closed by Windows?
  • Does your app need to resume as the user left it, rather than starting it fresh, following suspension?
  • Does your app need to start fresh if a long period of time has elapsed since the user last accessed it?
  • Should your app update the UI when resuming from suspension?
  • Does your app need to request data from a network or retrieve large amounts of data from disk when launched?

Your app must register to receive the Activated event in order to participate in activation. If your app needs to be activated through any contracts or extensions other than just normal launch by the user, you can use your app's Activated event handler to test to see how the app was activated. Examples of activation other than normal user launch include another app launching a file whose file type your app is registered to handle, and your app being chosen as the target for a share operation. For more info see Activating an app.

If your app needs to behave differently when it is closed by the user, rather than when it is closed by Windows, the Activated event handler can be used to determine whether the app was terminated by the user or by Windows. For more info see Activating an app.

Following suspension, most Windows Store apps should resume as the user left them rather than starting fresh. Explicitly saving your application data helps ensure that the user can resume your app even if Windows terminates it. It's a best practice to have your app save its state when it's suspended and restore its state when it's launched after termination. However, if your app was unexpectedly closed, assume that stored application data is possibly corrupt. The app should not try to resume but rather start fresh. Otherwise, restoring corrupt application data could lead to an endless cycle of activation, crash, and being closed. For more info see Guidelines for app suspend and resume (Windows Store apps), Suspending an app, Resuming an app, and Activating an app.

If there's a good chance that users won't remember or care about what was happening when they last saw your app, launch it from its default launch state. You must determine an appropriate period after which your app should start fresh. For example, a news reader app should start afresh if the downloaded news articles are stale. However, if there is any doubt about whether to resume or start fresh, you should resume the app right where the user left off. For more info see Resuming an app and Activating an app.

When resuming your app after it was suspended, update the UI if the content has changed since it was last visible to the user. This ensures that to the user the app appears as though it was running in the background. For more info see Resuming an app.

If your app needs to request data from a network or retrieve large amounts of data from disk, when the app is launched, these activities should be completed outside of activation. Use a custom loading UI or an extended splash screen while the app waits for these long running operations to finish. For more info see How to activate an app.

[Top]

Suspend and resume in AdventureWorks Shopper

The AdventureWorks Shopper reference implementation was designed to suspend correctly when the user moves away from it, or when Windows enters a low power state. It was also designed to resume correctly when the user moves back to it, or when Windows leaves the low power state. AdventureWorks Shopper uses the Microsoft.Practices.Prism.StoreApps library to provide both view and view model support for suspend and resume. This was achieved by:

  • Saving application data when the app is being suspended.
  • Resuming the app in the state that the user left it in.
  • Saving the page state to minimize the time required to suspend the app when navigating away from a page.
  • Allowing views and view models to save and restore state that's relevant to each. For example, AdventureWorks Shopper saves the scroll position of certain GridView controls as view state. This is achieved by overriding the SaveState and LoadState methods of the VisualStateAwarePage class in a view's class.
  • Using the saved application data to restore the app state, when the app resumes after being terminated.

For more info, see Guidelines for app suspend and resume (Windows Store apps).

[Top]

Understanding possible execution states

Which events occur when you activate an app depends on the app's execution history. There are five cases to consider. The cases correspond to the values of the Windows.ActivationModel.Activation.ApplicationExecutionState enumeration.

  • NotRunning
  • Terminated
  • ClosedByUser
  • Suspended
  • Running

The following diagram shows how Windows determines an app's execution state. In the diagram, the white rectangles indicate that the app isn't loaded into system memory. The blue rectangles indicate that the app is in memory. The dashed arcs are changes that occur without any notification to the running app. The solid arcs are actions that include app notification.

The execution state depends on the app's history. For example, when the user starts the app for the first time after installing it or after restarting Windows, the previous execution state is NotRunning, and the state after activation is Running. When activation occurs, the activation event arguments include a PreviousExecutionState property that indicates the state the app was in before it was activated.

If the user switches to a different app or if the system enters a low power mode of operation, Windows notifies the app that it's being suspended. At this time, you must save the navigation state and all user data that represents the user's session. You should also free exclusive system resources, like open files and network connections.

Windows allows 5 seconds for an app to handle the Suspending event. If the Suspending event handler doesn't complete within that amount of time, Windows behaves as though the app has stopped responding and terminates it. After the app responds to the Suspending event, its state is Suspended. If the user switches back to the app, Windows resumes it and allows it to run again.

Note  In Windows 8.1 if the user closes an app and then immediately restarts it, the closed app is given up to five seconds to complete its termination before it restarts.

 

Windows might terminate an app, without notification, after it has been suspended. For example, if the device is low on resources it might reclaim resources that are held by suspended apps. If the user launches your app after Windows has terminated it, the app's previous execution state at the time of activation is Terminated.

You can use the previous execution state to determine whether your app needs to restore the data that it saved when it was last suspended, or whether you must load your app's default data. In general, if the app stops responding or the user closes it, restarting the app should take the user to the app's default initial navigation state. When an app is activated after being terminated, it should load the application data that it saved during suspension so that the app appears as it did when it was suspended.

When an app is suspended but hasn't yet been terminated, you can resume the app without additional work as it will still be in memory.

For a description of the suspend and resume process, see Application lifecycle (Windows Store apps). For more info about each of the possible previous execution states, see the ApplicationExecutionState enumeration. You might also want to consult Guidelines for app suspend and resume (Windows Store apps) for info about the recommended user experience for suspend and resume.

[Top]

Implementation approaches for suspend and resume

For Windows Store apps such as the AdventureWorks Shopper reference implementation that use the Microsoft.Practices.Prism.StoreApps library, implementing suspend and resume involves four components:

  • Windows Core. The CoreApplicationView class's Activated event allows an app to receive activation-related notifications.
  • XAML. The Application class provides the OnLaunched method that your app's class should override to perform application initialization and to display the initial content. The Application class invokes the OnLaunched method when the user starts the app. When you create a new project for a Windows Store app using one of the Visual Studio project templates for C# apps, Visual Studio creates an App class that derives from Application and overrides the OnLaunched method. In MVVM apps such as AdventureWorks Shopper, much of the Visual Studio created code in the App class has been moved to the MvvmAppBase class that the App class then derives from.
  • Microsoft.Practices.Prism.StoreApps classes. If you base your MVVM app on the reusable classes of the Microsoft.Practices.Prism.StoreApps library, many aspects of suspend/resume will be provided for you. For example, the SessionStateService class will provide a way to save and restore state. If you annotate properties of your view models with the RestorableState custom attribute, they will automatically be saved and restored at the correct time. The SessionStateService also interacts with the Frame class to save and restore the app's navigation stack for you.
  • Your app's classes. View classes can save view state with each invocation of the OnNavigatedFrom method. For example, some view classes in AdventureWorks Shopper save user interface state such as scroll bar position. Model state is saved by view model classes, through the base ViewModel class.

Note  A user can activate an app through a variety of contracts and extensions. The Application class only calls the OnLaunched method in the case of a normal launch. For more info about how to detect other activation events see the Application class.

 

AdventureWorks Shopper does not directly interact with the CoreApplicationView class's activation-related events. We mention them here in case your app needs access to these lower-level notifications.

[Top]

Suspending an app

Suspension support is provided by the Microsoft.Practices.Prism.StoreApps library. In order to add suspension support to an app that derives from the MvvmAppBase class in this library, you only need to annotate properties of view models that you wish to save during suspension with the RestorableState custom attribute. In addition, if additional suspension logic is required you should override the OnNavigatedFrom method of the base ViewModel class. The following diagram shows the interaction of the classes that implement the suspend operation in AdventureWorks Shopper.

Here, the MvvmAppBase class registers a handler for the Suspending event that is provided by the Application base class.

Microsoft.Practices.Prism.StoreApps\MvvmAppBase.cs

this.Suspending += OnSuspending;

Windows invokes the OnSuspending event handler before it suspends the app. The MvvmAppBase class uses the event handler to save relevant app and user data to persistent storage.

Microsoft.Practices.Prism.StoreApps\MvvmAppBase.cs

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    IsSuspending = true;
    try
    {
        var deferral = e.SuspendingOperation.GetDeferral();

        //Bootstrap inform navigation service that app is suspending.
        NavigationService.Suspending();

        // Save application state
        await SessionStateService.SaveAsync();

        deferral.Complete();
    }
    finally
    {
        IsSuspending = false;
    }
}

The OnSuspending event handler is asynchronous. If a Suspending event's handler is asynchronous, it must notify its caller when its work has finished. To do this, the handler invokes the GetDeferral method that returns a SuspendingDeferral object. The Suspending method of the FrameNavigationService class is then called. The SessionStateService class's SaveAsync method then persists the app's navigation and user data to disk. After the save operation has finished, the Complete method of the SuspendingDeferral object is called to notify the operating system that the app is ready to be suspended. The following code example shows the Suspending method of the FrameNavigationService class.

Microsoft.Practices.Prism.StoreApps\MvvmAppBase.cs

public void Suspending()
{
    NavigateFromCurrentViewModel(true);
}

The Suspending method of the FrameNavigationService class calls the NavigateFromCurrentViewModel method that handles the suspension. The following code example shows the NavigateFromCurrentViewModel method of the FrameNavigationService class.

Microsoft.Practices.Prism.StoreApps\FrameNavigationService.cs

private void NavigateFromCurrentViewModel(bool suspending)
{
    var departingView = _frame.Content as FrameworkElement;
    if (departingView == null) return;
    var frameState = _sessionStateService.GetSessionStateForFrame(_frame);
    var departingViewModel = departingView.DataContext as INavigationAware;

    var viewModelKey = "ViewModel-" + _frame.BackStackDepth;
    if (departingViewModel != null)
    {
        var viewModelState = frameState.ContainsKey(viewModelKey)
                                 ? frameState[viewModelKey] as Dictionary<string, object>
                                 : null;

        departingViewModel.OnNavigatedFrom(viewModelState, suspending);
    }
}

The NavigateFromCurrentViewModel method gets the session state for the current view and calls the OnNavigatedFrom method on the current view model. All OnNavigatedFrom methods feature a suspending parameter that tells the view model whether it is being suspended. If the parameter is true it means that no change should be made to state that would invalidate the page and that a subsequent OnNavigatedTo method might not be called, for instance if the app resumes without being terminated. This allows you to implement additional functionality in view model classes that may be required when the OnNavigatedFrom method is called when the app isn't being suspended.

In the NavigateFromCurrentViewModel method the frameState dictionary is the dictionary for the frame. Each item in the dictionary is a view model that is at a specific depth in the frame back stack. Each view model also has its own state dictionary, viewModelState, that is passed to the view model's OnNavigatedFrom method. This approach is preferable to each view model creating entries in the frameState dictionary using the view models type as the key.

Saving view model state

All of the view model classes in the AdventureWorks Shopper reference implementation derive from the ViewModel class, provided by the Microsoft.Practices.Prism.StoreApps library, that implements the OnNavigatedFrom method. This method calls the FillStateDictionary method to add any view model state to the frame state, as shown in the following code example.

Microsoft.Practices.Prism.StoreApps\ViewModel.cs

public virtual void OnNavigatedFrom(Dictionary<string, object> viewModelState, bool suspending)
{
    if (viewModelState != null)
    {
        FillStateDictionary(viewModelState, this);
    }
}

The FillStateDictionary method iterates through any properties in the view model and stores the value of any properties that possess the [RestorableState] custom attribute. The SaveAsync method of the SessionStateService class then writes the current session state to disk.

Saving view state

The SaveAsync method calls the GetNavigationState method of each registered Frame object in order to persist the serialized navigation history (the frame stack). In AdventureWorks Shopper there is only one registered frame, and it corresponds to the rootFrame in the InitializeFrameAsync method in the MvvmAppBase class.

When the SaveAsync method calls the GetNavigationState method, it in turn invokes the OnNavigatedFrom method of each of the frame's associated page objects. The OnNavigatedFrom method in the VisualStateAwarePage class then invokes the SaveState method of any page that derives from it, allowing pages to save view state such as the current scroll position of a control.

AdventureWorks.Shopper\Views\HubPage.xaml.cs

protected override void SaveState(System.Collections.Generic.Dictionary<string, object> pageState)
{
    if (pageState == null) return;

    base.SaveState(pageState);

    pageState["scrollViewerOffsetProportion"] = ScrollViewerUtilities.GetScrollViewerOffsetProportion(_itemsGridViewScrollViewer);
}

Here, the SaveState method preserves state associated with the HubPage, in this case being a value that reflects the proportion of scrolling that has occurred either horizontally or vertically, depending on view state, within the ScrollViewer in the AutoRotatingGridView custom control. This value is retrieved by the GetScrollViewerOffsetProportion method in the ScrollViewerUtilities class. The value can then be restored when reactivation occurs.

Saving state from service and repository classes

Some service and repository classes also persist state to survive termination. In order to do this they use an instance of the SessionStateService class that implements the ISessionStateService interface. The following code example shows how the AccountService class persists the user's credentials.

AdventureWorks.UILogic\Services\AccountService.cs

_sessionStateService.SessionState[UserNameKey] = userName;
_sessionStateService.SessionState[PasswordKey] = password;

[Top]

Resuming an app

When an app resumes from the Suspended state, it enters the Running state and continues from where it was when it was suspended. No application data is lost, because it has not been removed from memory. Most apps don't need to do anything if they are resumed before they are terminated by the operating system.

The AdventureWorks Shopper reference implementation does not register an event handler for the Resuming event. In the rare case when an app does register an event handler for the Resuming event, the handler is called when the app resumes from the Suspended state.

[Top]

Activating an app

Activation support is provided by the Microsoft.Practices.Prism.StoreApps library. If Windows has terminated a suspended app, the Application base class calls the OnLaunched method when the app becomes active again. The following diagram shows the interaction of classes in AdventureWorks Shopper that restore the app after it has been terminated.

The MvvmAppBase class overrides the OnLaunched method of the Windows.UI.Xaml.Application base class. When the OnLaunched method runs, its argument is a LaunchActivatedEventArgs object. This object contains an ApplicationExecutionState enumeration that tells you the app's previous execution state. The OnLaunched method calls the InitializeFrameAsync method to initialize the app's Frame object. The following code example shows the relevant code from the InitializeFrameAsync method.

Microsoft.Practices.Prism.StoreApps\MvvmAppBase.cs

if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
    await SessionStateService.RestoreSessionStateAsync();
}

OnInitialize(args);
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
    // Restore the saved session state and navigate to the last page visited
    try
    {
        SessionStateService.RestoreFrameState();
        NavigationService.RestoreSavedNavigation();
        _isRestoringFromTermination = true;
    }
    catch (SessionStateServiceException)
    {
        // Something went wrong restoring state.
        // Assume there is no state and continue
    }
}

The code checks its argument to see whether the previous state was Terminated. If so, the method calls the SessionStateService class's RestoreSessionStateAsync method to recover saved settings. The RestoreSessionStateAsync method reads the saved state info, and then the OnInitialize method is called which is overridden in the App class. This method registers instances and types with the Unity dependency injection container. Then, if the previous execution state of the app was Terminated, the saved session state is restored and the app navigates to the last page was that visited prior to termination. This is achieved by calling the RestoreSavedNavigation method of the FrameNavigationService class that in turn simply calls the NavigateToCurrentViewModel method, which gets the session state for the current view, and calls the OnNavigatedTo method on the current view model.

Microsoft.Practices.Prism.StoreApps\FrameNavigationService.cs

private void NavigateToCurrentViewModel(NavigationMode navigationMode, object parameter)
{
    var frameState = _sessionStateService.GetSessionStateForFrame(_frame);
    var viewModelKey = "ViewModel-" + _frame.BackStackDepth;

    if (navigationMode == NavigationMode.New)
    {
        // Clear existing state for forward navigation when adding a new page/view model to the
        // navigation stack
        var nextViewModelKey = viewModelKey;
        int nextViewModelIndex = _frame.BackStackDepth;
        while (frameState.Remove(nextViewModelKey))
        {
            nextViewModelIndex++;
            nextViewModelKey = "ViewModel-" + nextViewModelIndex;
        }
    }

    var newView = _frame.Content as FrameworkElement;
    if (newView == null) return;
    var newViewModel = newView.DataContext as INavigationAware;
    if (newViewModel != null)
    {
        Dictionary<string, object> viewModelState;
        if (frameState.ContainsKey(viewModelKey))
        {
            viewModelState = frameState[viewModelKey] as Dictionary<string, object>;
        }
        else
        {
            viewModelState = new Dictionary<string, object>();
        }
        newViewModel.OnNavigatedTo(parameter, navigationMode, viewModelState);
        frameState[viewModelKey] = viewModelState;
    }
}

Restoring view model state

All of the view model classes in the AdventureWorks Shopper reference implementation derive from the ViewModel base class, provided by the Microsoft.Practices.Prism.StoreApps library, which implements the OnNavigatedTo method. This method simply calls the RestoreViewModel method to restore any view model state from the frame state, as shown in the following code example.

Microsoft.Practices.Prism.StoreApps\ViewModel.cs

public virtual void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState)
{
    if (viewModelState != null)
    {
        RestoreViewModel(viewModelState, this);
    }
}

The RestoreViewModel method iterates through any properties in the view model and restores the values of any properties that possess the [RestorableState] attribute, from the frame state.

Restoring view state

Saved session state is restored by the RestoreFrameState method in the SessionStateService class. This method calls the SetNavigationState method of each registered Frame object in order to restore the serialized navigation history (the frame stack). In AdventureWorks Shopper there is only one registered frame, and it corresponds to the rootFrame in the InitializeFrameAsync method in the MvvmAppBase class.

When the RestoreFrameState method calls the SetNavigationState method, it in turn invokes the OnNavigatedTo method of each of the frame’s associated page objects. The OnNavigatedTo method in the VisualStateAwarePage class then invokes the LoadState method of any page that derives from it, allowing pages to restore view state such as the current scroll position of a control.

Microsoft.Practices.Prism.StoreApps\ViewModel.cs

protected override void LoadState(object navigationParameter, System.Collections.Generic.Dictionary<string, object> pageState)
{
    if (pageState == null) return;

    base.LoadState(navigationParameter, pageState);

    if (pageState.ContainsKey("scrollViewerOffsetProportion"))
    {
        _scrollViewerOffsetProportion = double.Parse(pageState["scrollViewerOffsetProportion"].ToString(), CultureInfo.InvariantCulture.NumberFormat);                
    }
}

Here, the LoadState method restores state associated with the HubPage, in this case a value that reflects the proportion of horizontal or vertical scrolling that had occurred, depending on view state, within the ScrollViewer in the AutoRotatingGridView custom control. The ScrollViewer is set with the restored value by the ScrollToProportion method in the ScrollViewerUtilites class once the window has rendered or changed its rendering size. Therefore, the user will see the page content scrolled to the exact location it was at prior to termination or navigation, regardless of whether the page orientation has changed in between termination and reactivation.

Restoring state from service and repository classes

Some service and repository classes also restore state that was previously persisted to survive termination. In order to do this they use an instance of the SessionStateService class that implements the ISessionStateService interface. The following code example shows how the AccountService class restores the user's credentials.

AdventureWorks.UILogic\Services\AccountService.cs

if (_sessionStateService.SessionState.ContainsKey(UserNameKey))
{
    _userName = _sessionStateService.SessionState[UserNameKey].ToString();
}
if (_sessionStateService.SessionState.ContainsKey(PasswordKey))
{
    _password = _sessionStateService.SessionState[PasswordKey].ToString();
}

[Top]

Other ways to close the app

Apps don't contain UI for closing the app, but users can choose to close an app by pressing Alt+F4, dragging the app to the bottom of the screen, or selecting the Close context menu for the app when it's in the sidebar. When an app is closed by any of these methods, it enters the NotRunning state for approximately 10 seconds and then transitions to the ClosedByUser state.

Apps shouldn't close themselves programmatically as part of normal execution. When you close an app programmatically, Windows treats this as an app crash. The app enters the NotRunning state and remains there until the user activates it again.

The following diagram shows how Windows determines an app's execution state. Windows takes app crashes and user close actions into account, as well as the suspend or resume state. In the diagram, the white rectangles indicate that the app isn't loaded into system memory. The blue rectangles indicate that the app is in memory. The dashed lines are changes that occur without any modification to the running app. The solid lines are actions that include app notification.

[Top]