Reacting to Lifecycle Events

We have already discussed the application model and its attendant lifecycle. Refer to Figure 23.11. We have three possible application states:

Image Activated

Image Suspended

Image Not Running

At the application level, you are notified of app changes via a series of events that correspond to the arrows you see in Figure 23.11. Handling these events and reacting appropriately means you need to write some event handlers; your project’s App class is your vehicle for hooking these events. In fact, the standard App.xaml.cs file created for you already contains code to wire up the Suspending event.

public App()
{
    this.InitializeComponent();
    this.Suspending += OnSuspending;
}

private void OnSuspending(object sender, SuspendingEventArgs e)
{
    var deferral = e.SuspendingOperation.GetDeferral();
    //TODO: Save application state and stop any background activity
    deferral.Complete();
}

The use of the deferral object may seem confusing at first, but its job is fairly simple. While running your program, when the end of the OnSuspending routine is reached, the runtime will assume that you have taken care of everything that needs to be taken care of and will promptly suspend the application. But if your application has followed good practice, your state saving activity will be executed asynchronously. And that means that the OnSuspending routine could conclude before your async activity has actually completed.

The SuspendingDeferral object, which is returned from the call shown above to e.SuspendingOperation.GetDeferral, is used to signal to Windows that you want to explicitly tell the runtime when you are done with your state housekeeping. There is a caveat here: Windows will suspend your application regardless of your deferral object if you take longer than approximately 5 seconds to complete your work. So in essence, having the deferral object created means “don’t suspend the application until I tell you to, or until my 5 seconds are up, whichever comes first.”

The flip side of the Suspending event, when an application is being resumed, is the Resuming event, which looks similar. (You need to add this yourself; it isn’t included automatically.)

this.Resuming += OnResuming;

Finally, the OnLaunched routine is called when your application is launched. This could be by a user clicking/tapping the app tile, or it could be because a user is going back to your app after it has been suspended and then terminated.

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
    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();

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            //TODO: Load state from previously suspended application.
        }

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

    if (rootFrame.Content == null)
    {
        //When the navigation stack isn't restored navigate to the first page,
        //configuring the new page by passing required information as a
        //navigation parameter.
        if (!rootFrame.Navigate(typeof(MainPage), args.Arguments))
        {
            throw new Exception("Failed to create initial page");
        }
    }
    //Ensure the current window is active.
    Window.Current.Activate();
}

Storing State

Once your application is aware of these events, you can react to them appropriately. There is no stock answer here in terms of how you should read and write your applications state. But the simple high-level pattern is this: when your application is suspending, do a final save of its state, and when it is restarted after termination, restore the state. One attractive option is the use of local storage. Each application has default permissions to access the local storage area. (In other words, it isn’t a capability that needs to be explicitly declared.) For our image viewing app, if we wanted to store the page name of the current page, along with the filename of any currently loaded image, we could do that quite easily by a) creating a general object to store those items and b) serializing that object into local storage.

Saving into the application storage area can be accomplished via the familiar StorageFile class and serializer (commonly, DataContractSerializer). A great way to bootstrap your application state storage development is to take a look at a helper class delivered by Microsoft, called SuspensionManager. This class maintains a Dictionary object that in turn contains the objects making up your application’s state. If you add your state information to its dictionary, you can then call a SaveAsync method on the class, which will take care of serializing everything to disk:

//Save the current session state.
static async public Task SaveAsync()
{
    //Get the output stream for the SessionState file.
    StorageFile file = await
        ApplicationData.Current.LocalFolder.CreateFileAsync(filename,
        CreationCollisionOption.ReplaceExisting);

    using (StorageStreamTransaction transaction = await
        file.OpenTransactedWriteAsync())
    {
        //Serialize the session state.
        DataContractSerializer serializer = new
            DataContractSerializer(typeof(Dictionary<string, object>),
               knownTypes_);

        serializer.WriteObject(transaction.Stream.AsStreamForWrite(),
            sessionState_);

        await transaction.CommitAsync();
    }
}

Similarly, you can rehydrate your state information via its RestoreAsync method.

//Restore the saved session state.
static async public Task RestoreAsync()
{
    //Get the input stream for the SessionState file.
    try
    {
        StorageFile file = await
           ApplicationData.Current.LocalFolder.GetFileAsync(filename);

        if (file == null) return;

        using (IInputStream inStream = await file.OpenSequentialReadAsync())
        {
            //Deserialize the session state.
            DataContractSerializer serializer = new
               DataContractSerializer(typeof(Dictionary<string, object>),
                  knownTypes_);

            sessionState_ = (Dictionary<string,
               object>)serializer.ReadObject(inStream.AsStreamForRead());
        }
    }
    catch (Exception)
    {
        //Restoring state is best-effort. If it fails, the app will
        //just come up with a new session.
    }
}

As mentioned previously in this chapter, remember that if you are dealing with anything more than a moderate amount of data in your application, you should consider saving that data regardless of whether any of the lifecycle events have been triggered. You don’t want to get into a scenario in which the time it takes to save your data is longer than the allotted window for either application startup or suspension. In the case of the former, Windows will assume that the app is hung and will kill it. And in the case of the latter, you might not get all your data committed before the application process disappears. If the application is then subsequently terminated, you have now permanently lost data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset