Condividi tramite


How to continue your Windows Phone Store app after calling an AndContinue method

[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]

Certain memory-intensive API on Windows Phone 8.1 contain AndContinue methods. When you call an AndContinue method, your app is deactivated while the operation completes in order to conserve memory. On low-memory devices, your app might even be terminated. Because of this possibility, you have to call different methods in a Windows Phone Store app than you call in a Windows Store app to continue your app after these operations. The following table shows these methods.

Task Method to call from a Windows Store app Method to call from a Windows Phone Store app
Pick a file to open PickSingleFileAsync PickSingleFileAndContinue
Pick a location and filename to save a file PickSaveFileAsync PickSaveFileAndContinue
Pick a folder PickSingleFolderAsync PickFolderAndContinue
Connect to an online identity provider AuthenticateAsync AuthenticateAndContinue

 

The example in this topic demonstrates how to continue your app after calling one of these methods.

The solution depends on the following two things. The example uses two helper classes to reduce the amount of code you have to write each time your use a file picker or connect to an online identity provider.

  • Save and restore application state. The SuspensionManager class simplifies lifecycle management for your app. To get the SuspensionManager class, create a new Windows Phone 8.1 project that uses a project template other than the Blank App template. The SuspensionManager.cs file is in the Common folder of the project. To learn more about the SuspensionManager class in the context of a Windows Store app, see Part 2: Manage app lifecycle and state of the Getting Started series.
  • Remember what the user was doing and finish the task when the app continues. The custom ContinuationManager class includes interfaces and methods that make it easier to continue your app.

Tip  To see a complete example of this solution, see the File picker sample.

 

Important  Complete code for the custom ContinuationManager helper class and for the app.xaml.cs file that calls the 2 helper classes is shown later in this topic. If you copy these files into your project, make sure you update the namespace in each file to match the namespace of your project.

 

Instructions

To continue your app

  1. Include the SuspensionManager helper class in your project. This class simplifies lifecycle management for your app. The complete code for the SuspensionManager class is shown at the bottom of this topic.

  2. Include a helper class like the ContinuationManager class in your project. This class includes interfaces and methods that make it easier to continue your app. To get the ContinuationManager class file, download the File picker sample. The complete code for the ContinuationManager class is also shown at the bottom of this topic.

  3. In the app.xaml.cs file, declare an instance of the ContinuationManager helper class at the class level.

            public ContinuationManager ContinuationManager { get; private set; }
    
  4. In the app.xaml.cs file, instantiate the instance of the ContinuationManager helper class in the OnActivated event handler.

            protected async override void OnActivated(IActivatedEventArgs e)
            {
                ...
                ContinuationManager = new ContinuationManager();
                ...
            }
    
  5. Assume that one page of your app contains code that calls a FileOpenPicker to pick an existing file. In this class, implement the corresponding interface from the ContinuationManager helper class. When your app uses a FileOpenPicker, the interface to implement is IFileOpenPickerContinuable.

        public sealed partial class Scenario1 : Page, IFileOpenPickerContinuable
        {
            ...
        }
    
  6. Call the AndContinue method.

    For example, the following demonstrates calling PickSingleFileAndContinue.

            private void PickAFileButton_Click(object sender, RoutedEventArgs e)
            {
                ...
                FileOpenPicker openPicker = new FileOpenPicker();
                openPicker.ViewMode = PickerViewMode.Thumbnail;
                openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
                openPicker.FileTypeFilter.Add(".jpg");
                openPicker.FileTypeFilter.Add(".jpeg");
                openPicker.FileTypeFilter.Add(".png");
    
                // Launch file open picker and caller app is suspended
                // and may be terminated if required
                openPicker.PickSingleFileAndContinue();
            }
    
  7. Your app is suspended after calling the AndContinue method. The handler for the Application.Suspending event in the app.xaml.cs file calls the SaveAsync method of the SuspensionManager helper class to save application state.

  8. The user completes a task, such as selecting a file using the FileOpenPicker or entering credentials into the WebAuthenticationBroker. When they finish, your app is reactivated. In the app.xaml.cs file, in the OnActivated event handler, restore application state by calling the RestoreAsync method of the SuspensionManager helper class. Then check whether this activation is a continuation. If this activation is a continuation, call the Continue method of the ContinuationManager helper class.

            protected async override void OnActivated(IActivatedEventArgs e)
            {
                base.OnActivated(e);
    
                continuationManager = new ContinuationManager();
    
                Frame rootFrame = CreateRootFrame();
                await RestoreStatusAsync(e.PreviousExecutionState);
    
                if(rootFrame.Content == null)
                {
                    rootFrame.Navigate(typeof(MainPage));
                }
    
                var continuationEventArgs = e as IContinuationActivatedEventArgs;
                if (continuationEventArgs != null)
                {
                    Frame scenarioFrame = MainPage.Current.FindName("ScenarioFrame") as Frame;
                    if (scenarioFrame != null)
                    {
                        // Call ContinuationManager to handle continuation activation
                        continuationManager.Continue(continuationEventArgs, scenarioFrame);
                    }
                }
    
                Window.Current.Activate();
            }
    
  9. The switch statement in the ContinuationManager helper class determines what kind of reactivation is occurring.

            switch (args.Kind)
            {
                case ActivationKind.PickFileContinuation:
                    var fileOpenPickerPage = rootFrame.Content as IFileOpenPickerContinuable;
                    if (fileOpenPickerPage != null)
                    {
                        fileOpenPickerPage.ContinueFileOpenPicker(args as FileOpenPickerContinuationEventArgs);
                    }
                    break;
    
                case ActivationKind.PickSaveFileContinuation:
                    var fileSavePickerPage = rootFrame.Content as IFileSavePickerContinuable;
                    if (fileSavePickerPage != null)
                    {
                        fileSavePickerPage.ContinueFileSavePicker(args as FileSavePickerContinuationEventArgs);
                    }
                    break;
    
                case ActivationKind.PickFolderContinuation:
                    var folderPickerPage = rootFrame.Content as IFolderPickerContinuable;
                    if (folderPickerPage != null)
                    {
                        folderPickerPage.ContinueFolderPicker(args as FolderPickerContinuationEventArgs);
                    }
                    break;
    
                case ActivationKind.WebAuthenticationBrokerContinuation:
                    var wabPage = rootFrame.Content as IWebAuthenticationContinuable;
                    if (wabPage != null)
                    {
                        wabPage.ContinueWebAuthentication(args as WebAuthenticationBrokerContinuationEventArgs);
                    }
                    break;
            }
    
  10. Your app then calls a custom Continue method, such as ContinueFileOpenPicker or ContinueWebAuthentication.. You implement this Continue method in the same page of your app that called the AndContinue method. TheContinue method processes the results of the operation. For more information on how to implement a specific continuation method, see the following topics:

To debug your app before and after it calls the AndContinue method

Follow these steps to test the case in which your app is terminated after calling the AndContinue method. These steps ensure that the debugger reattaches to your app after completing the operation and continuing.

  1. In Visual Studio, right-click on your project and select Properties.
  2. In Project Designer, on the Debug tab under Start action, enable Do not launch, but debug my code when it starts.
  3. Run your app with debugging. This deploys the app, but does not run it.
  4. Start your app manually. The debugger attaches to the app. If you have breakpoints in your code, the debugger stops at the breakpoints. When your app calls the AndContinue method, the debugger continues to run.
  5. If your app calls a file picker, wait until you have opened the file provider (for example, Phone, Photos, or OneDrive). If your app calls an online identity provider, wait until the authentication page opens.
  6. On the Debug Location toolbar, in the Process dropdown list, select the process for your app. In the Lifecycle Events dropdown list, select Suspend and Shutdown to terminate your app but leave the emulator running.
  7. After the AndContinue operation completes, the debugger reattaches to your app automatically when the app continues.

Complete code for the ContinuationManager class and app.xaml.cs file

Here is the complete code for the custom ContinuationManager helper class. To get the ContinuationManager helper class, download the File picker sample. If you copy this code into your project, make sure you update the namespace in the file to match the namespace of your project.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

#if WINDOWS_PHONE_APP
    /// <summary>
    /// ContinuationManager is used to detect if the most recent activation was due
    /// to a continuation such as the FileOpenPicker or WebAuthenticationBroker
    /// </summary>
public class ContinuationManager
{
    IContinuationActivatedEventArgs args = null;
    Guid id = Guid.Empty;

    /// <summary>
    /// Sets the ContinuationArgs for this instance. Using default Frame of current Window
    /// Should be called by the main activation handling code in App.xaml.cs
    /// </summary>
    /// <param name="args">The activation args</param>
    internal void Continue(IContinuationActivatedEventArgs args)
    {
        Continue(args, Window.Current.Content as Frame);
    }

    /// <summary>
    /// Sets the ContinuationArgs for this instance. Should be called by the main activation
    /// handling code in App.xaml.cs
    /// </summary>
    /// <param name="args">The activation args</param>
    /// <param name="rootFrame">The frame control that contains the current page</param>
    internal void Continue(IContinuationActivatedEventArgs args, Frame rootFrame)
    {
        if (args == null)
            throw new ArgumentNullException("args");

        if (this.args != null && !handled)
            throw new InvalidOperationException("Can't set args more than once");

        this.args = args;
        this.id = Guid.NewGuid();

        if (rootFrame == null)
            return;

        switch (args.Kind)
        {
            case ActivationKind.PickFileContinuation:
                var fileOpenPickerPage = rootFrame.Content as IFileOpenPickerContinuable;
                if (fileOpenPickerPage != null)
                {
                    fileOpenPickerPage.ContinueFileOpenPicker(args as FileOpenPickerContinuationEventArgs);
                }
                break;

            case ActivationKind.PickSaveFileContinuation:
                var fileSavePickerPage = rootFrame.Content as IFileSavePickerContinuable;
                if (fileSavePickerPage != null)
                {
                    fileSavePickerPage.ContinueFileSavePicker(args as FileSavePickerContinuationEventArgs);
                }
                break;

            case ActivationKind.PickFolderContinuation:
                var folderPickerPage = rootFrame.Content as IFolderPickerContinuable;
                if (folderPickerPage != null)
                {
                    folderPickerPage.ContinueFolderPicker(args as FolderPickerContinuationEventArgs);
                }
                break;

            case ActivationKind.WebAuthenticationBrokerContinuation:
                var wabPage = rootFrame.Content as IWebAuthenticationContinuable;
                if (wabPage != null)
                {
                    wabPage.ContinueWebAuthentication(args as WebAuthenticationBrokerContinuationEventArgs);
                }
                break;
        }
    }

    /// <summary>
    /// Retrieves the continuation args, if they have not already been retrieved, and 
    /// prevents further retrieval via this property (to avoid accidentla double-usage)
    /// </summary>
    public IContinuationActivatedEventArgs ContinuationArgs
    {
        get
        {
            return args;
        }
    }

    /// <summary>
    /// Unique identifier for this particular continuation. Most useful for components that 
    /// retrieve the continuation data via <see cref="GetContinuationArgs"/> and need
    /// to perform their own replay check
    /// </summary>
    public Guid Id { get { return id; } }

}

/// <summary>
/// Implement this interface if your page invokes the file open picker
/// API.
/// </summary>
interface IFileOpenPickerContinuable
{
    /// <summary>
    /// This method is invoked when the file open picker returns picked
    /// files
    /// </summary>
    /// <param name="args">Activated event args object that contains returned files from file open picker</param>
    void ContinueFileOpenPicker(FileOpenPickerContinuationEventArgs args);
}

/// <summary>
/// Implement this interface if your page invokes the file save picker
/// API
/// </summary>
interface IFileSavePickerContinuable
{
    /// <summary>
    /// This method is invoked when the file save picker returns saved
    /// files
    /// </summary>
    /// <param name="args">Activated event args object that contains returned file from file save picker</param>
    void ContinueFileSavePicker(FileSavePickerContinuationEventArgs args);
}

/// <summary>
/// Implement this interface if your page invokes the folder picker API
/// </summary>
interface IFolderPickerContinuable
{
    /// <summary>
    /// This method is invoked when the folder picker returns the picked
    /// folder
    /// </summary>
    /// <param name="args">Activated event args object that contains returned folder from folder picker</param>
    void ContinueFolderPicker(FolderPickerContinuationEventArgs args);
}

/// <summary>
/// Implement this interface if your page invokes the web authentication
/// broker
/// </summary>
interface IWebAuthenticationContinuable
{
    /// <summary>
    /// This method is invoked when the web authentication broker returns
    /// with the authentication result
    /// </summary>
    /// <param name="args">Activated event args object that contains returned authentication token</param>
    void ContinueWebAuthentication(WebAuthenticationBrokerContinuationEventArgs args);
}

#endif

Here is complete code for the app.xaml.cs file that uses the ContinuationManager and SuspensionManager helper classes. To get the app.xaml.cs file, download the File picker sample. If you copy this code into your project, make sure you update the namespace in the file to match the namespace of your project.

// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using SDKTemplate.Common;

namespace SDKTemplate
{
    /// <summary>
    /// Provides application-specific behavior to supplement the default Application class.
    /// </summary>
    sealed partial class App : Application
    {
#if WINDOWS_PHONE_APP
        ContinuationManager continuationManager;
#endif
        /// <summary>
        /// Initializes the singleton Application object.  This is the first line of authored code
        /// executed, and as such is the logical equivalent of main() or WinMain().
        /// </summary>
        public App()
        {
            this.InitializeComponent();
            this.Suspending += OnSuspending;
        }

        private Frame CreateRootFrame()
        {
            Frame rootFrame = Window.Current.Content as Frame;

            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (rootFrame == null)
            {
                // Create a Frame to act as the navigation context and navigate to the first page
                rootFrame = new Frame();

                // Set the default language
                rootFrame.Language = Windows.Globalization.ApplicationLanguages.Languages[0];
                rootFrame.NavigationFailed += OnNavigationFailed;

                // Place the frame in the current Window
                Window.Current.Content = rootFrame;
            }

            return rootFrame;
        }

        private async Task RestoreStatusAsync(ApplicationExecutionState previousExecutionState)
        {
            // Do not repeat app initialization when the Window already has content,
            // just ensure that the window is active
            if (previousExecutionState == ApplicationExecutionState.Terminated)
            {
                // Restore the saved session state only when appropriate
                try
                {
                    await SuspensionManager.RestoreAsync();
                }
                catch (SuspensionManagerException)
                {
                    //Something went wrong restoring state.
                    //Assume there is no state and continue
                }
            }
        }

#if WINDOWS_PHONE_APP
        /// <summary>
        /// Handle OnActivated event to deal with File Open/Save continuation activation kinds
        /// </summary>
        /// <param name="e">Application activated event arguments, it can be casted to proper sub-type based on ActivationKind</param>
        protected async override void OnActivated(IActivatedEventArgs e)
        {
            base.OnActivated(e);

            continuationManager = new ContinuationManager();

            Frame rootFrame = CreateRootFrame();
            await RestoreStatusAsync(e.PreviousExecutionState);

            if(rootFrame.Content == null)
            {
                rootFrame.Navigate(typeof(MainPage));
            }

            var continuationEventArgs = e as IContinuationActivatedEventArgs;
            if (continuationEventArgs != null)
            {
                Frame scenarioFrame = MainPage.Current.FindName("ScenarioFrame") as Frame;
                if (scenarioFrame != null)
                {
                    // Call ContinuationManager to handle continuation activation
                    continuationManager.Continue(continuationEventArgs, scenarioFrame);
                }
            }

            Window.Current.Activate();
        }
#endif

        /// <summary>
        /// Invoked when the application is launched normally by the end user.  Other entry points
        /// will be used such as when the application is launched to open a specific file.
        /// </summary>
        /// <param name="e">Details about the launch request and process.</param>
        protected async override void OnLaunched(LaunchActivatedEventArgs e)
        {
            Frame rootFrame = CreateRootFrame();
            await RestoreStatusAsync(e.PreviousExecutionState);

            //MainPage is always in rootFrame so we don't have to worry about restoring the navigation state on resume
            rootFrame.Navigate(typeof(MainPage), e.Arguments);

            // Ensure the current window is active
            Window.Current.Activate();
        }

        /// <summary>
        /// Invoked when Navigation to a certain page fails
        /// </summary>
        /// <param name="sender">The Frame which failed navigation</param>
        /// <param name="e">Details about the navigation failure</param>
        void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
        }

        /// <summary>
        /// Invoked when application execution is being suspended.  Application state is saved
        /// without knowing whether the application will be terminated or resumed with the contents
        /// of memory still intact.
        /// </summary>
        /// <param name="sender">The source of the suspend request.</param>
        /// <param name="e">Details about the suspend request.</param>
        private async void OnSuspending(object sender, SuspendingEventArgs e)
        {
            var deferral = e.SuspendingOperation.GetDeferral();
            await SuspensionManager.SaveAsync();
            deferral.Complete();
        }
    }
}

How to continue your Windows Phone app after calling a file picker

File picker sample