共用方式為


Handling suspend, resume, and activation in Hilo (Windows Store apps using C++ and XAML)

From: Developing an end-to-end Windows Store app using C++ and XAML: Hilo

Previous page | Next page

Hilo provides examples of how to suspend and resume an app that uses C++ and XAML. You can use these examples to write an app that fully manages its execution life cycle. Suspension can happen at any time, and when it does you need to save your app's data so that the app can resume correctly.

Download

After you download the code, see Getting started with Hilo for instructions.

You will learn

  • How the app's activation history affects its behavior.
  • How to implement support for suspend and resume using C++ and XAML.

Applies to

  • Windows Runtime for Windows 8
  • Visual C++ component extensions (C++/CX)
  • XAML

Tips for implementing suspend/resume

You should design your app to suspend correctly when the user switches away from it, or when there is a low power state. You should also design the app to resume correctly when the user switches back to it, or when Windows leaves the low power state. Here are some points to remember.

  • Save application data when the app is being suspended.
  • Resume your app to the state the user left it in, rather than starting afresh.
  • Allow views and view models to save and restore state that is relevant to each. For example, if the user has typed text into a text box but has not yet tabbed out of the text box, you may want to save the partially entered text as view state. In Hilo, only view models need to save state.
  • Release exclusive resources when the app is being suspended.
  • When the app resumes, update the UI if the content has changed.
  • When the app resumes after it was terminated, use the saved application data to restore the app state.

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

Here’s a diagram that shows how Windows determines the app’s execution state. In the diagram, the blue rectangles indicate that the app is not loaded into system memory. The white rectangles indicate that the app is in memory. The dashed arcs are changes that occur without any notification to your 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 launches 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. The activation event arguments include a PreviousExecutionState property that tells you the state your 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 is 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 such as open files and network connections.

Windows allows 5 seconds for the app to handle the Suspending event. If the Suspending event handler does not complete within that amount of time, Windows assumes that 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.

Windows may terminate the app (without notification) after it has been suspended. For example, if the system is low on resources it might decide to 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 crashes or the user closes it, restarting the app should take the user to the app’s default initial navigation state. When an app determines that it 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. You can see how this works in the code walkthrough of Hilo's support for suspend and resume on this page.

Note  When the app is in suspended, but hasn’t yet been terminated, you can resume the app without restoring any state. The app will still be in memory. In this situation, you might need to reacquire resources and update the UI to reflect any changes to the environment that have occurred while the app was suspended. For example, Hilo updates its view of the user's Pictures when resuming from the suspended state.

 

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

[Top]

Implementation approaches for suspend and resume in C++ and XAML

For Windows Store apps such as Hilo that use a Visual Studio project template, implementing suspend/resume involves four components of the system.

  • Windows Core. The Windows::ApplicationModel::Core::CoreApplicationView class’s Activated event allows an app to receive activation-related notifications.
  • XAML. The Windows::UI::Xaml::Application class provides the OnLaunched method that your app’s App class should override to perform application initialization and to display its initial content. XAML’s 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 Microsoft Visual Studio project templates for C++ and XAML, Visual Studio creates an App class that derives from Windows::UI::Xaml::Application and overrides the OnLaunched method. You can specialize the code that Visual Studio creates, or use it without changes.
  • Visual Studio template classes. When you create a new project for a Windows Store app, Visual Studio creates several classes for you in the Common solution folder. One of these classes is the SuspensionManager class. This class provides methods that work with the Windows::UI::Xaml::Application class to make it convenient to save and restore app state. The SuspensionManager class interacts with the Windows::UI::Xaml::Frame class to save and restore the app’s navigation stack for you. The SuspensionManager class interacts with the LayoutAwarePage template class to integrate saving page state at the appropriate times.
  • Your app’s page classes. Hilo saves navigation state with each invocation of their OnNavigatedFrom method. Hilo pages delegate saving state to their associated view model classes. Hilo view models implement the LoadState and SaveState methods.

Note  Hilo 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.

 

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. See the Windows::UI::Xaml::Application class for more info about how to detect other kinds of activation events. In Hilo we only needed to handle normal launch.

 

The rest of this page is a walkthrough of how Hilo implements suspend and resume by using these layers of the system.

[Top]

Code walkthrough of suspend

This diagram shows the interaction of the classes that implement the suspend operation in Hilo.

When Hilo starts, it registers a handler for the Suspending event that is provided by the Application base class.

App.xaml.cpp

Suspending += ref new SuspendingEventHandler(this, &App::OnSuspending);

Windows invokes the App::OnSuspending event handler before it suspends the app. Hilo uses the event handler to save relevant app and user data to persistent storage.

App.xaml.cpp

void App::OnSuspending(Object^ sender, SuspendingEventArgs^ e)
{
    (void) sender; // Unused parameter
    assert(IsMainThread());

    auto deferral = e->SuspendingOperation->GetDeferral();
    HiloPage::IsSuspending = true;
    SuspensionManager::SaveAsync().then([=](task<void> antecedent)
    {
        HiloPage::IsSuspending = false;
        antecedent.get();
        deferral->Complete();
    });
}

Hilo's Suspending event’s 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 and calls this object's Complete method at the end of the async operation.

The SuspensionManager class’s SaveAsync method persists the app’s navigation and user data to disk. After the save operation has finished, a continuation task notifies the deferral object that the app is ready to be suspended.

Note  In Hilo, we defined the HiloPage::IsSuspending static property to establish context when the app receives callbacks from the SaveAsync method. We need to know if the OnNavigatedFrom method is called in the case of normal operation, or if it is being called while saving state for suspend/resume. For example, when the user navigates away from a page, the app sometimes needs to disconnect event handlers to that page. We don't want to disconnect event handlers when suspending.

 

The SaveAsync method of the SuspensionManager class writes the current session state to disk. The SaveAsync method calls the GetNavigationState method of each registered Frame object. In Hilo, there is only a registered frame, and it corresponds to the current page. The GetNavigationState method invokes OnNavigatedFrom method of the frame’s associated page object.

In Hilo, each page is an instance of the HiloPage class. Here is this class's OnNavigatedFrom method.

HiloPage.cpp

void Hilo::HiloPage::OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e)
{
    ViewModelBase^ viewModel = dynamic_cast<ViewModelBase^>(DataContext);
    if (!HiloPage::IsSuspending) 
    {        
        // Since we never receive destructs, this is the only place to unsubscribe.
        DetachNavigationHandlers(viewModel);
        viewModel->OnNavigatedFrom(e);
    }

   LayoutAwarePage::OnNavigatedFrom(e);
}

The OnNavigatedFrom method of the HiloPage class updates the view models and then invokes the OnNavigatedFrom method of its base class, LayoutAwarePage.

LayoutAwarePage.cpp

void LayoutAwarePage::OnNavigatedFrom(NavigationEventArgs^ e)
{
    auto frameState = SuspensionManager::SessionStateForFrame(Frame);
    auto pageState = ref new Map<String^, Object^>();
    SaveState(pageState);
    frameState->Insert(_pageKey, pageState);
}

The OnNavigatedFrom method of the LayoutAwarePage class gets an object from the suspension manager and sets it as the value of the frameState variable. This object implements the Windows::Foundation::Collections::IMap<String^, Object^> interface. It represents all of the state that has been collected so far during the SaveAsync operation. Next, the code calls the page’s SaveState method to augment the frameState object with information for the current page.

Note  The OnNavigatedFrom method is called during the suspend operation but also for ordinary navigation while the app is running. Hilo also uses the SaveState and LoadState methods to support navigating back to a known state. For example, after the user crops an image and returns to the image view page, restoring the state on navigation lets the image view page display the correct photo.

 

Here is the SaveState method.

HiloPage.cpp

void HiloPage::SaveState(IMap<String^, Object^>^ pageState)
{
    auto vm = dynamic_cast<ViewModelBase^>(DataContext);
    if (vm != nullptr)
    {
        auto vmStateMap = ref new Map<String^, Object^>();
        vm->SaveState(vmStateMap);

        pageState->Insert(viewModelStateKey, vmStateMap);
    }
}

In Hilo, the page delegates the saving of state to its associated view model, and uses a single key/value pair for the entire page's data. For example, if the currently displayed page is the image view page, the applicable method is the SaveState method of Hilo's ImageViewModel class.

ImageViewModel.cpp

void ImageViewModel::SaveState(IMap<String^, Object^>^ stateMap)
{
    if (m_currentPhotoImage != nullptr)
    {
        std::wstringstream stringSerialization;
        stringSerialization << m_monthDate.UniversalTime ;

        stateMap->Insert(FilePathMapKey, m_currentPhotoImage->Path);
        stateMap->Insert(FileDateMapKey, ref new String(stringSerialization.str().c_str()));
        stateMap->Insert(QueryMapKey, m_query);
    }
}

This code shows how the image view model class saves three pieces of information: the path and name of the current image, the date of the current month group, and the query that was used. Other view models save states that are relevant to their operations.

You can use any serialization technique that you want, but the end result must be converted into a Platform::Object^ reference. You can choose any keys that you want for the key/value pairs. Just be sure to use keys that are unique within the current page, and use the same keys when you later restore the data.

[Top]

Code walkthrough of resume

When an app is resumed from the Suspended state, it enters the Running state and continues from where it was when it was suspended. No application data is lost, as it was stored in memory, so most apps don't need to do anything when they are resumed.

It is possible that the app being resumed has been suspended for hours or even days. So, if the app has content or network connections that may have gone stale, these should be refreshed when the app resumes. When an app is suspended, it does not receive network or file event items that it registered to receive. These events are not queued, but are simply ignored. In this situation, your app should test the network or file status when it resumes. The app might also refresh location-dependent state such as a map position.

If an app registered an event handler for the Resuming event, it is called when the app resumes from the Suspended state. You can refresh your content by using this event handler. Hilo subscribes to the Resuming event to refresh its view of Pictures.

App.xaml.cpp

Resuming += ref new EventHandler<Platform::Object^>(this, &App::OnResume);

Here is the App::OnResume member function.

App.xaml.cpp

void App::OnResume(Object^ sender, Platform::Object^ e)
{
    (void) sender; // Unused parameter
    (void) e;      // Unused parameter
    assert(IsMainThread());

    if (m_repository != nullptr)
    {
        // Hilo does not receive data change events when suspended. Create these events on resume.
        m_repository->NotifyAllObservers();
    }
}

This code tells the app's file respository object that it should generate data-change events for view models that need to respond to file system changes. Hilo doesn't attempt to detect data changes that occurred while it was suspended.

[Top]

Code walkthrough of activation after app termination

If Windows has terminated a suspended app, the Application base class calls the OnLaunched method when the app becomes active again. This diagram shows the interaction of classes in Hilo that restore the app after it has been terminated.

Hilo’s App class overrides the OnLaunched method of Windows::UI::Xaml::Application base class. When your App class’s OnLaunched method runs, its argument is a LaunchActivatedEventArgs object. This object contains an ApplicationExecutionState enumeration that tells you the app’s previous execution state. Here's the code.

App.xaml.cpp

void App::OnLaunched(LaunchActivatedEventArgs^ args)
{
    assert(IsMainThread());
    auto rootFrame = dynamic_cast<Frame^>(Window::Current->Content);

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == nullptr)
    {
        // Create a Frame to act as the navigation context and associate it with
        // a SuspensionManager key. See https://go.microsoft.com/fwlink/?LinkId=267280 for more info 
        // on Hilo's implementation of suspend/resume.
        rootFrame = ref new Frame();
        SuspensionManager::RegisterFrame(rootFrame, "AppFrame");

        auto prerequisite = task<void>([](){});
        if (args->PreviousExecutionState == ApplicationExecutionState::Terminated)
        {
            // Restore the saved session state only when appropriate, scheduling the
            // final launch steps after the restore is complete
            prerequisite = SuspensionManager::RestoreAsync();
        }
        prerequisite.then([=](task<void> prerequisite)
        {
            try
            {
                prerequisite.get();
            }
            catch (Platform::Exception^)
            {
                //Something went wrong restoring state.
                //Assume there is no state and continue
            }

            if (rootFrame->Content == nullptr)
            {
                // When the navigation stack isn't restored navigate to the first page,
                // configuring the new page by passing required information as a navigation
                // parameter.  See https://go.microsoft.com/fwlink/?LinkId=267278 for a walkthrough of how 
                // Hilo creates pages and navigates to pages.
                if (!rootFrame->Navigate(TypeName(MainHubView::typeid)))
                {
                    throw ref new FailureException((ref new LocalResourceLoader())->GetString("ErrorFailedToCreateInitialPage"));
                }
            }

            // Place the frame in the current Window
            Window::Current->Content = rootFrame;
            // Ensure the current window is active
            Window::Current->Activate();

        }, task_continuation_context::use_current());
    }
    else
    {
        if (rootFrame->Content == nullptr)
        {
            // When the navigation stack isn't restored navigate to the first page,
            // configuring the new page by passing required information as a navigation
            // parameter. See https://go.microsoft.com/fwlink/?LinkId=267278 for a walkthrough of how 
            // Hilo creates pages and navigates to pages.
            if (!rootFrame->Navigate(TypeName(MainHubView::typeid)))
            {
                throw ref new FailureException((ref new LocalResourceLoader())->GetString("ErrorFailedToCreateInitialPage"));
            }
        }
        // Ensure the current window is active
        Window::Current->Activate();
    }

    // Schedule updates to the tile. See https://go.microsoft.com/fwlink/?LinkId=267275 for
    // info about how Hilo manages tiles.
    m_tileUpdateScheduler = std::make_shared<TileUpdateScheduler>();
    m_tileUpdateScheduler->ScheduleUpdateAsync(m_repository, m_exceptionPolicy);
}

The code checks its argument to see whether the previous state was Terminated. If so, the method calls the SuspensionManager class's RestoreAsync method to recover saved settings from the _sessionState.dat file.

The RestoreAsync method reads the saved state info and then calls the SetNavigationState method for each registered frame that was previously saved. See Code walkthrough of suspend on this page to see how the save happens.

The SetNavigationState method of each frame calls the OnNavigatedTo method of its associated page. Here is how the HiloPage class overrides the OnNavigatedTo method.

HiloPage.cpp

void HiloPage::OnNavigatedTo(NavigationEventArgs^ e)
{
    
    ViewModelBase^ viewModel = dynamic_cast<ViewModelBase^>(DataContext);
    this->AttachNavigationHandlers(viewModel);
    if (viewModel != nullptr)
    {
        viewModel->OnNavigatedTo(e);
    }

    LayoutAwarePage::OnNavigatedTo(e);
}

The Hilo page calls the OnNavigatedTo method of its associated view model. For example, if the currently displayed page is the image view page, the applicable method is the OnNavigatedTo method of Hilo's ImageViewModel class. Here is the code.

ImageViewModel.cpp

void ImageViewModel::OnNavigatedTo(NavigationEventArgs^ e)
{
    auto data = dynamic_cast<String^>(e->Parameter);
    ImageNavigationData imageData(data);
    Initialize(imageData.GetFilePath(), imageData.GetMonthGroupDate(), imageData.GetDateQuery());
}

In this example, the code extracts the saved navigation information from the argument and updates the view model.

After the view model's OnNavigatedTo method has finished, the HiloPage object invokes the OnNavigatedTo method of its base class, LayoutAwarePage.

LayoutAwarePage.cpp

void LayoutAwarePage::OnNavigatedTo(NavigationEventArgs^ e)
{
    // Returning to a cached page through navigation shouldn't trigger state loading
    if (_pageKey != nullptr) return;

    auto frameState = SuspensionManager::SessionStateForFrame(Frame);
    _pageKey = "Page-" + Frame->BackStackDepth;

    if (e->NavigationMode == NavigationMode::New)
    {
        // Clear existing state for forward navigation when adding a new page to the
        // navigation stack
        auto nextPageKey = _pageKey;
        int nextPageIndex = Frame->BackStackDepth;
        while (frameState->HasKey(nextPageKey))
        {
            frameState->Remove(nextPageKey);
            nextPageIndex++;
            nextPageKey = "Page-" + nextPageIndex;
        }

        // Pass the navigation parameter to the new page
        LoadState(e->Parameter, nullptr);
    }
    else
    {
        // Pass the navigation parameter and preserved page state to the page, using
        // the same strategy for loading suspended state and recreating pages discarded
        // from cache
        LoadState(e->Parameter, safe_cast<IMap<String^, Object^>^>(frameState->Lookup(_pageKey)));
    }
}

The LayoutAwarePage gets the previously saved state for the page from the SuspensionManager class. It then calls the page's LoadState method to deserialize the page's saved state and restore it. Here is the code for the LoadState method from the HiloPage class and the ImageViewModel class.

HiloPage.cpp

void HiloPage::LoadState(Object^ navigationParameter, IMap<String^, Object^>^ pageState)
{
    auto vm = dynamic_cast<ViewModelBase^>(DataContext);
    if (vm != nullptr && pageState != nullptr)
    {
        IMap<String^, Object^>^ state = nullptr;
        state = dynamic_cast<IMap<String^, Object^>^>(pageState->Lookup(viewModelStateKey));

        vm->LoadState(state);
    }
}

ImageViewModel.cpp

void ImageViewModel::LoadState(IMap<String^, Object^>^ stateMap)
{
    if (stateMap != nullptr)
    {
        auto filePath = dynamic_cast<String^>(stateMap->Lookup(FilePathMapKey));

        auto fileDateString = dynamic_cast<String^>(stateMap->Lookup(FileDateMapKey));
        DateTime fileDate;
        fileDate.UniversalTime = _wtoi64(fileDateString->Data());

        auto query = dynamic_cast<String^>(stateMap->Lookup(QueryMapKey));

        Initialize(filePath, fileDate, query);
    }
}

This code deserializes the previously saved information for the image view model. You can compare it with the code that originally saved the data, which appears above in Code walkthrough of suspend on this page.

[Top]

Other ways to exit the app

Apps do not contain UI for closing the app, but users can choose to close an app by pressing Alt+F4, dragging the app from the Start Page, or selecting the Close context menu. When an app has been closed using 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.

Your app must follow the system crash experience, which is to simply return to the Start Page.

Here’s a diagram that shows how Windows determines the 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 blue rectangles indicate that the app is not loaded into system memory. The white rectangles indicate that the app is in memory. The dashed arcs are changes that occur without any notification to your running app. The solid arcs are actions that include app notification.

[Top]