(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Optimize Windows Store application startup with a waiting splash screen

(2 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   72
Comments:   9
More Articles
0 comments   /   posted on Jan 22, 2013
Categories:   Windows 8
Tweet

Let say you have developed a Windows Store app. It has been extensively tested to ensure that every problem have been addressed and, finally, after spending a long time to fill out every field of the submission form, you are ready to press the fatidical "submit for certification" button to start the certification process. I know, you are totally aware that this first submission is almost a bet that you have an high probability to lose. The certification process, often stops the deployment of the application for unexpected reasons, and lot of times these are recurring problems that you would have skipped with a minimum experience.

From my experience there is a number of requirements, detailed in the "Windows 8 app certification requirements" that recur from time to time and of the most common is the startup time. If you reach the point 3.8 in the document you will find the following sentence:

Given that you did all in your capabilities to reduce to the minimum this period, reducing to the max the load of external resources, the deserialization of suspension data and so on, you finally run out of ideas just because, lot of times, there is nothing else to remove and the startup continue to overcome the 5 seconds limit. And definitely there are lot of cases, expecially for the very first application startup, where it is impossible to be respectful of this restrictive timing. As an example, you may have the need of loading some resources from the network to create a local cache that make your app working offline.

Understanding startup time

The first step to resolve these situations is to have clear in mind how it is measures the startup time. When the application launches, the very first line of user code hit is the App constructor. Please note that this is probably the worst point to put your code, and if you have problems with application startup, it is the first place to check. Inside the constructor first thing are really legal. Probably the hooking of event handlers and initialization of the app.xaml that comes from the InitializeComponents method are the sole things you should put here.

Then, after the constructor has been executed, a number of other methods can enter the scene, but if you are in the normal startup, from NotRunning state, the runtime goes to the OnLaunched method. Usually it is the place where the user code initializes the Windows, loads the Frame and then navigates to the first page, just before to activate the window. Here is the basic code generated by the Visual Studio template:

   1: public App()
   2: {
   3:     this.InitializeComponent();
   4:     this.Suspending += OnSuspending;
   5: }
   6:  
   7: /// <summary>
   8: /// Invoked when the application is launched normally by the end user.  Other entry points
   9: /// will be used when the application is launched to open a specific file, to display
  10: /// search results, and so forth.
  11: /// </summary>
  12: /// <param name="args">Details about the launch request and process.</param>
  13: protected override void OnLaunched(LaunchActivatedEventArgs args)
  14: {
  15:     Frame rootFrame = Window.Current.Content as Frame;
  16:  
  17:     // Do not repeat app initialization when the Window already has content,
  18:     // just ensure that the window is active
  19:     if (rootFrame == null)
  20:     {
  21:         // Create a Frame to act as the navigation context and navigate to the first page
  22:         rootFrame = new Frame();
  23:  
  24:         if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
  25:         {
  26:             //TODO: Load state from previously suspended application
  27:         }
  28:  
  29:         // Place the frame in the current Window
  30:         Window.Current.Content = rootFrame;
  31:     }
  32:  
  33:     if (rootFrame.Content == null)
  34:     {
  35:         // When the navigation stack isn't restored navigate to the first page,
  36:         // configuring the new page by passing required information as a navigation
  37:         // parameter
  38:         if (!rootFrame.Navigate(typeof(MainPage), args.Arguments))
  39:         {
  40:             throw new Exception("Failed to create initial page");
  41:         }
  42:     }
  43:     // Ensure the current window is active
  44:     Window.Current.Activate();
  45: }

For the purpose of calculating the startup time, the checkered flag of your run is the call to Activate() at the end of the OnLaunched method. Calling this method you are saying to the runtime that the load has end and it can start the rendering. If you set a number of breakpoints in the code of the blank app, you will see that the MainPage constructor and also the OnNavigatedTo method, are called before the OnLaunched method goes to the end, so all the code you put inside the first page initialization is also part of the startup time. If inside the page you put a long-running code that creates the layout, or simply you use a complex layout for the page itself this may lead to the overcome of the startup time. Also you can have some code to prepare or restore the state of the app just inside the OnLaunched method and this is a huge contribution to slow down the startup.

So how can you handle these situations, given that sometimes you cannot oversimplify your startup? Probably the first answer you may have is so simple to seems trivial: it just suffice to not do these tasks... at least not before the activation has gone to the end.

Handle startup properly

If the solution of avoid loading the app startup before the activation completes seems trivial, it is not only a sensation. Just think at the how the user feels this solution: he probably would see the splash screen (the one with the application logo) to take a long time to disappear to leave room for the first page and this is not really a good idea because in this situation the possibility that the user terminates the app, navigating away (or explicitly closing it) is not so remote.

The answer is to replace the start screen with a similar, lightweight page that adds a waiting overlay (for example a progress ring) to give the impression that the application is not blocked but is doing some "important" tasks to prepare the user interface. This is not so easy as it may seems but the framework helps you providing the needed information. So first of all let create a new user control that we will call CustomSplashScreen.

   1: <UserControl
   2:     x:Class="Elite.EarthquakeAlerts.UserControls.CustomSplashScreen"
   3:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   5:     xmlns:local="using:Elite.EarthquakeAlerts.UserControls"
   6:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   7:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   8:     mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
   9:     
  10:     <UserControl.Resources>
  11:         <Storyboard x:Name="exit">
  12:             <DoubleAnimation To="0" Duration="00:00:00.500" Storyboard.TargetName="backRect" Storyboard.TargetProperty="Opacity" />
  13:         </Storyboard>
  14:     </UserControl.Resources>
  15:     
  16:     <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
  17:     
  18:         <Rectangle Height="10" Fill="{StaticResource TitleForegroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Top" />
  19:         <Rectangle x:Name="backRect" Grid.RowSpan="2" Fill="{StaticResource TitleForegroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
  20:     
  21:         <Canvas>
  22:             <Image x:Name="logoImage" Source="../Assets/Images/splash.png" Width="620" Height="300" />
  23:         </Canvas>
  24:     
  25:         <ProgressRing Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" IsActive="True" 
  26:                       Foreground="{StaticResource ApplicationForegroundThemeBrush }" Width="30" Height="30" Margin="0,350,0,0" />
  27:     
  28:     </Grid>
  29:     
  30: </UserControl>

In this page I've mainly a Canvas with an Image that references the same bitmap used for the default splash screen. The use of the Canvas is to have a fast loading layout that not requires the calculation that is behind StackPanels and Grids. We will have to deal with absolute positioning but this should be light that using one of the other panels. Also in he interface I have a ProgressRing that continuously rotate to give the correct feedback to the user. The other elements in the page are optional and demonstrates only that you can also add some beautiful effects without impacting too much the load time. Then we have to go to the code behind where the trick is mainly done.

   1: public CustomSplashScreen(IActivatedEventArgs arguments)
   2: {
   3:     this.Arguments = arguments;
   4:     this.SplashScreen = arguments.SplashScreen;
   5:  
   6:     InitializeComponent();
   7:  
   8:     if (SplashScreen != null)
   9:     {
  10:         Window.Current.SizeChanged += HandleResize;
  11:         this.UpdateImagePosition();
  12:     }
  13:  
  14:     this.DoLoadingAsync();
  15: }

The constructor of this user control will receive the instance of IActivatedEventArgs. This may be the argument of each activation event like OnLaunched, OnSearchActivated, OnFileActivated, etc... Inside of this arguments there is a reference to the current SplashScreen instance. This reference is used to calculate the position of the image to be equal to those in the splash screen. Fnally the code runs to the DoLoadingAsync method where the application is loaded:

   1: private async void DoLoadingAsync()
   2: {
   3:     if (this.Arguments.PreviousExecutionState == ApplicationExecutionState.Terminated)
   4:     {
   5:         try
   6:         {
   7:             await SuspensionManager.RestoreAsync();
   8:         }
   9:         catch (SuspensionManagerException)
  10:         { }
  11:     }
  12:  
  13:     // here goes your long time tasks
  14:     await Task.Delay(5000);
  15:  
  16:     this.NavigateToStartPage();
  17: }

In this method all the required content is loaded calling methods with the "await" modifier. In the previous snippet it is identified by a simple delaty of 5 seconds but in this place you will put the long time tasks. In you run synchronous action you get to block the UI thread and the trick does not work. At the end of your loading the application navigate away calling the NavigateToStartPage method.

   1: private void NavigateToStartPage()
   2: {
   3:     this.exit.Completed += exit_Completed;
   4:     this.exit.Begin();
   5: }
   6:  
   7: private void exit_Completed(object sender, object e)
   8: {
   9:     Frame theFrame = new Frame();
  10:     theFrame.Navigate(typeof(MainPage), null);
  11:     Window.Current.Content = theFrame;
  12: }

In the exit method I start a fast animation. In my example the animation fade out the background of the CustomSplashScreen to simulate the exit of the default splash screen. You can for sure add your own animation to improve this example but remember to be really lightweight. Finally remember the control has to absolutely position the image so we have to handle the Resize event in case of screen rotation.

   1: private void HandleResize(Object sender, WindowSizeChangedEventArgs e)
   2: {
   3:     this.UpdateImagePosition();
   4: }
   5:  
   6: private void UpdateImagePosition()
   7: {
   8:     Canvas.SetLeft(this.logoImage, this.SplashScreen.ImageLocation.X);
   9:     Canvas.SetTop(this.logoImage, this.SplashScreen.ImageLocation.Y);
  10:     this.logoImage.Height = this.SplashScreen.ImageLocation.Height;
  11:     this.logoImage.Width = this.SplashScreen.ImageLocation.Width;
  12: }

What and when...

After reading this you can ask yourself what to load and when to use this example. It depends from the needs of your application. Most of the times, when you have a connected app you need to preload some informations. In my last application, Earthquake Alerts, I initialize the application and download the first update of the earthquake data. This only happens the very first time the user starts the application because the choice is to show the cached content all the other times. For this purpose I wrote a simple CacheManager that handle a local file that is the copy of the last valid downloaded data. The first time this file does not exists so, during the CustomSplashScreen time I download this data and save it in the cache file. All the other times I firstly show the content from the cache and run a download in the background.

After reading this you can ask yourself what to load and when to use this example. It depends from the needs of your application. Most of the times, when you have a connected app you need to preload some informations. In my last application, Earthquake Alerts, I initialize the application and download the first update of the earthquake data. This only happens the very first time the user starts the application because the choice is to show the cached content all the other times. For this purpose I wrote a simple CacheManager that handles a local file that is the copy of the last valid downloaded data. The first time, this file does not exists, so during the CustomSplashScreen time I download this data and save it in the cache file. All the other times I firstly show the content from the cache, then run an update download in the background.

Another task that is possible to do is to create complex layouts that needs a number of calculation. This does not means this layout is rendered during the time of loading but only that the elements are put together each other. The rendering time will starts after the splash screen has dismissed and the page has been loaded. So probably this only handles half the problem. In these cases the better is to design the page to support lazy or incremental loading taking advantage of the fact that the user is not able to view the entire content at once, but often needs to scroll to the right or open other elements.

Finally you can also prepare contents that require computational load. Draw images to prepare cached versions of them may be an example.

Be always careful

The solution is for sure an important extension to the normal load time. Please have clear in mind that this extension is not infinite. The extra time you gained does not depends on computational requirements but is strictly connected with the user feedback. Also if something is rotating suggesting to the user that something is in progress, on the long time he will be tempted to terminate tha app. So please be careful to give the correct message. In you think you may stay undes 10 seconds the example is good as-is, but for longest time the better is to give an extended feedback that includes a progress percentage. Then the waiting will be directly proportional to the added value your application will give to the end user.


Subscribe

Comments

No comments

Add Comment

Login to comment:
  *      *       

From this series