This repo contains demo application implementing advanced Universal Windows Platform (UWP) lifecycle patterns related to "Launching, resuming, and background tasks" MSDN section. Application is organized according to layered architecture using MVVM and Inversion-of-Control/Dependency-Inversion/Dependency-Injection.
There are few important UWP topics that should be reviewed to make architectural decisions.
Microsoft had a chance to build application platform from scratch to meet all potential requirements to mobile applications in XXI century. Developers and other users expect that "Windows 10" will be renamed to "Windows" (number less) and user will not have to buy next OS. That may happen when Win10 market share will reach over 1 billion of users. Windows 10S - is the sample of such free OS where you will pay only for store applications. It is nice to see that Microsoft has a good will to do such innovations. In different Win10 annual builds we see permanent progress in UWP evolution.
UWP is very different than classic windows application. UWP as Win8-x/Win10 is part of Microsoft's "Mobile First and Cloud First" concept. UWP, as WinRT superset, has strong abstraction from core OS kernel process entity. In comparison to classic windows applications, API calls were referenced to WinRT interfaces. Theoretically, in next 5 years UWP may be ported to other OS platforms as Android.
UWP, first of all, differs from other application kinds in its lifetime. Classic UWP application has started/stopping lifecycle points available for handling from application code. To understand UWP lifecycle you should forget everything that you knew about application lifecycle. Basically, UWP has Not Running, Running and Suspended states. Additionally we may handle Foreground and Background states.
What did inspire Microsoft engineers to make such lifecycle? I may say that classic Windows/Unix application lifecycle invitation was inspired by some first electric equipment that could be turned ON or OFF. UWP lifecycle is something like lifecycle of human thoughts that exists in creative human head. They may be suspended if computer doesn't have enough resources (Battery or RAM) and be resumed later. When app moves to suspend it should release OS resources (like file handles) because process cannot know if it will be resumed at all. Win10 OS on different devices (Xbox or Desktop) and different builds (10.0 Build 10586, Anniversary Update - 10.0 Build 14393, eg.) has different heuristic behavior to manage application lifecycle. Some Windows 10 versions may freeze and close process without suspension. Other OS versions may suspend, hibernate and reload your application after computer restart. UWP developer should carefully read all "Launching, resuming, and background tasks" MSDN section and test different form factors and Win10 builds.
Extended execution is related to application lifecycle but becomes separate serious topic in UWP application architecture. Extended execution is very important technique for any application logic that executes during one second and longer. Theoretically its usage is simple for basic case but there are many practical questions:
- Developer wants to write logic in the way maximally decoupled from extended execution session manner according to Dependency Inversion principle.
- Applications often have multiple parallel running processes. It incites developer to open two parallel extended execution sessions. However UWP allows requesting only one extended execution session in the same time. Attempt to open the second session before disposing the first one will rise
InvalidOperationException
. - Application logic needs some flexible enough abstraction over single extended execution session. Application logic needs some manager that controls single extended execution session lifetime. Such manager should be able to host multiple tasks.
- Depending on windows battery charge, energy save mode enable, "Battery usage by app" settings and other OS factors, application may call
RequestExtensionAsync()
method and getExtendedExecutionResult.Denied
. It doesn't mean that without extended execution application should not allow user to execution some regular long running context that may be denied or revoked after start. - Every aggregated under extended execution manager task should be notified about shared extended execution session revoke and may handle such event in different ways:
- Implement task cancellation using
CancellationToken
. - return from task's incremental long running loop.
- notify user with toast notification and continue execution without suspension protection.
- Implement task cancellation using
- End user knows nothing neither about sophisticated UWP lifecycle no about extended execution nuances. Regular user may change "Battery usage by app" settings but that should not change foreground application execution user experience.
Developer should care about all these aspects if application may do some work after it was minimized.
Memory pleasure handling for me is an advanced programming topic that was described in few good books like:
- Jeffrey Richter "Windows via C/C++" book
- Joe Duffy "Concurrent Programming on Windows" book
- Kalen Delaney "Microsoft SQL Server 2012 Internals" book
In classic Win32 application under memory pleasure, you may start getting OutOfMomoryExceptions
. Additionally, UWP application may be suspended after rising MemoryManager.AppMemoryUsageLimitChanging
event during background execution. The best sample of application where such approach may be used is SQL Server (extremely complex software) where memory is allocated as blocks (extends) and managed in very advanced way. When we are developing much simpler UWP application that attempts to free resources to don’t be suspended, application handle such case:
- According to MSDN guidelines app may unload some data and View Layer if application is in background executions state.
- Try to avoid executing critical work sections in Mobile applications to avoid problems. It is natural for UWP application to be suspended/resumed and most of application tasks execution will not be harmed by suspend.
- Consider how your application layered architecture should be designed to release consumed RAM.
Idea of unloading View layer (setting windows content to null
and сollecting all related memory) is very new. In UWP application View layer memory may be collected without Application class instance.
How real is such situation in real world mobile application? On the beginning of 2017 Microsoft releases new Surface Pro and Surface Laptop devices with 4GB RAM. It is very real that some student will run some heavy application like Auto Cad and your application RAM limit will be decreased to 250MB. Other, more frequent sample, is when Xbox runs some heavy game that takes maximum RAM and the box will allow your application be alive only if your application will take 100MB maximum. Does it makes sense for your application to stay alive with minimal amount of RAM? Sometimes YES when you are developing Skype competitor that should hold minimal communication with online server. If your application also requires lots of ram and is not designed to release majority of it and restore it later on the move to foreground, maybe, it will make sense to let Windows to suspend your app and store its RAM to the disk.
Anyway, there are few ways of what application can do when MemoryManager.AppMemoryUsageLimitChanging
event will be riced with arguments warning about memory usage overflow:
- Ignore this fact and expect that application will be suspended and even terminated.
- Free some not required for minimal execution data controlled by Application Logic.
- Unload View level objects according to Guidelines.
MVVM as a pattern is very popular in XAML world.
In UWP on Windows 10 University Update (Build 14393) MVVM bindings may be implemented using new x:Bind (Compiled Binding) markup extension. Compiled bindings usage improves performance and debugging experience but doesn't change MVVM principles. Compiled Event Binding usage is important nuance for layered MVVM architecture. MVVM ViewModel can be .NET Standard class without System.Windows.Input.ICommand
interface usage from Windows.Foundation.UniversalApiContract
UWP API contract.
Dependency Inversion is one of fundamental SOLID principles to make application development process agile (in adjective meaning). It is useful to know the difference and relations between Inversion-of-Control/Dependency Inversion/Dependency-Injection terms. To lear better this topic we would recommend "Dependency Injection in .NET" book by Mark Seeman. This books contains lost of nuances and DI implementation samples for different platforms e.g. WPF.
This article describes how Universal Application architectural topics, described above, may be combined in one solution.
Demo application has following features:
- Application domain logic should be built according to Inversion-of-Control and Dependency-Injection principles and has no coupling to UWP.
- Ideally if application logic will reside in .NET Standard projects.
- Dependency Injection Composition Root should be compatible with .NET Native that removes type metadata required for reflection. DI Containers use reflection to construct dependencies. That's why in UWP it is better to use Manual Composition Root implementation or register types and namespaces for reflection.
- Application logic should be aware about application lifecycle events.
- Application logic should be able o work with Extended Execution Session.
- Application logic is the long living part of process in comparison to View layer that may be unloaded.
- Adapt ViewModel for Compiled bindings.
- Control primary and secondary UWP Views (Windows) from application logic.
- View Model should be able to control View Frame/Pages navigation.
- Logic that may be shared between few UWP applications should be extracted to application blocks.
Application Logic term used in this article means everything related to application code what is not related to View layer and UWP. We may pick few synonyms as: Business Logic, Core Logic, Domain Logic. Application Logic contains ViewModel infrastructure. In this sample ViewModel has no coupling neither to UWP packages no View layer project. Solution contains 4 projects:
Application block contains two reusable projects:
ApplicationLogicAbstractions
- Interfaces used by application logic to control environment.ApplicationLogicEnvironment
- Simplifies UWP application initialization. Controls application execution starting fromIApplicationLogicFactory
object.
Solution also contains two Demo application projects:
Demo.ApplicationLogic
- resides application logic. Application Logic starts its execution fromIApplicationLogicFactory
interface implementation.Demo.UniversalWindowsApplication
- UWP application startup project contains:
- XAML and code behind files.
- Dependency Injection composition root.
- Map providing to get XAML Page by view model.
IApplicationLogicFactory
is the starting point of application used byApplicationLogicEnvironment
.IApplicationLogicFactory
providesIApplicationLogic
- the root application logic object.IApplicationLogic
object may be constructed usingIApplicationLogicAgent
. The simplestIApplicationLogicFactory
may have default constructor and constructIApplicaitonLogic
instance. InDemo.UniversalWindowsApplication
project sampleApplicationLogicFactory
is constructed by Dependency Injection Composition Root and gets additional arguments likeISemanticLogger
interface.IApplicationLogicAgent
provides all control over Application Logic Environment like application lifecycle.ApplicationLogicAgent
also allows to open new UWP Views/Windows.IApplicationLogic
- is the most long leaving application logic object because it exists until UWP application termination. That's why it may store shared data state that may survive even after View layer unload.IApplicationLogic
providesIWindowFrameControllerFactory
only for primary UWP View/Window. Application logic may start secondary windows usingIApplicationLogicAgent.OpenNewSecondaryViewAsync
method later according to Show multiple views guide.IWindowFrameControllerFactory
providesIWindowFrameController
.IWindowFrameController
object may be constructed usingIWindowFrameControllerAgent
.IWindowFrameControllerAgent
- manages window content and executes tasks in View/Windows thread dispatcher. The agent also may show View dialogs.IWindowFrameController
is constructed to control window content.IWindowFrameController
providesIPageViewModelFactory
to provide initial window page. Application block assumes that UWP window always has Frame as root visual tree element and it needs Page for initial navigation.IPageViewModelFactory
provides page view model and identifies view to associate with view model type. This interface is used to navigate Frame to Page usingWindows.UI.Xaml.Controls.Frame.Navigate(Type sourcePageType, object parameter)
method.
Essence of relations between main abstractions may be described as:
- DI composition root provides
IApplicationLogicFactory
IApplicationLogicFactory
+IApplicationLogicAgent
=>IApplicationLogic
IApplicationLogic.PrimaryWindowFrameControllerFactory
+IWindowFrameControllerAgent
=>IWindowFrameController
IWindowFrameController.StartPageViewModelFactory
provides ViewModel mand Page View identifier.
To use this application block you should instantiate ApplicationManager
and call its ApplicationManager.OnLaunched
method from Windows.UI.Xaml.Application.OnLaunched
method. ApplicationManager
will subscribe to Application
object events and will control application execution. ApplicationManager
constructor takes following arguments:
IApplicationLogicFactory
implementation to build application logic object.Windows.UI.Xaml.Application
instance.Func\<Guid, Type>
to get view type by view identifier associated inIPageViewModelFactory
.
/// <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 override void OnLaunched(LaunchActivatedEventArgs e)
{
Windows.ApplicationModel.Core.CoreApplication.EnablePrelaunch(true);
if (applicationManager == null)
{
applicationManager = new ApplicationManager(
() => CompositionRoot.Instance.GetApplicationLogicFactory(),
this,
(Guid key) => pageViewMap.Value[key]
);
}
applicationManager.OnLaunched(e);
}
IApplicationLogicFactory
is built by Dependency Injection Composition Root located in CompositionRoot
class.
"The term Inversion of Control (IoC) originally meant any sort of programming style where an overall framework or runtime controlled the program flow." (Martin Fowler, “InversionOfControl,” 2005)
ApplicationManager
class in combination with IApplicationLogicFactory
implementation provides Inversion of Control mechanism because ApplicationManager
controls IApplicationLogic
execution flow.
ApplicationManager
injects main UWP dependencies into IApplicationLogicFactory
using agents e.g. IApplicationLogicAgent
and IWindowFrameControllerAgent
. If application logic needs some UWP specific functionality like Windows.System.MemoryManager
class, developer may create some abstraction to get memory information in Demo.ApplicationLogic
project, use it from application logic but implementation will reside it in Demo.UniversalWindowsApplication
project.
public interface IApplicationMemoryManager
{
ulong AppMemoryUsage { get; }
event EventHandler<AppMemoryUsageLimitChangingEventArgs> AppMemoryUsageLimitChanging;
}
...
internal sealed class ApplicationMemoryManger : IApplicationMemoryManager
{
public ApplicationMemoryManger()
{
MemoryManager.AppMemoryUsageLimitChanging +=
(sender, e) => OnAppMemoryUsageLimitChanging(e.NewLimit, e.OldLimit);
}
public ulong AppMemoryUsage => MemoryManager.AppMemoryUsage;
public event EventHandler<ApplicationLogic.AppMemoryUsageLimitChangingEventArgs> AppMemoryUsageLimitChanging;
private void OnAppMemoryUsageLimitChanging(ulong newLimit, ulong oldLimit) =>
AppMemoryUsageLimitChanging?.Invoke(
this,
new ApplicationLogic.AppMemoryUsageLimitChangingEventArgs(newLimit, oldLimit)
);
}
IPageViewModelFactory
interface implementation resides in Demo.ApplicationLogic
project that doesn’t have reference to Page view but has to provide some Guid identifier. Demo.UniversalWindowsApplication
project contains Page View XAML and can provide type save association between view identifier and View type.
private static Dictionary<Guid, Type> GetPageViewMap() => new Dictionary<Guid, Type>
{
{
ApplicationLogic.MainPage.MainPageViewModelFactory.PageTypeId,
typeof(MainPage)
},
{
ApplicationLogic.OrganisationCentric.OrganisationCentricViewModelFactory.PageTypeId,
typeof(OrganisationCentric.OrganisationCentricPageForSecondaryWindow)
},
{
ApplicationLogic.OrganisationCentric.OrganisationCentricPageViewModelFactory.PageTypeId,
typeof(OrganisationCentric.OrganisationCentricPageForMainWindow)
},
};
Such map should contain key-value pairs for all possible IPageViewModelFactory
that may be navigated to.
Application block makes lots of windows and Frame Page navigation but few things like applying ViewModel to out page should be done manually in Page code behind.
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public MainPageViewModel ViewModel { get; private set; }
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var prameter = (PageNavitedToParameters)e.Parameter;
ViewModel = (MainPageViewModel)prameter.ViewModel;
}
}
Application logic may be notified about all application lifecycle events from IApplicationLogicAgent
evens: Suspension
, Resument
, EnteredBackground
, LeavingBackground
. These events are called directly from Windows.UI.Xaml.Application
event handlers.
IApplicationLogicAgent
provides IExtendedExecutionSessionFactory
used to request Extended Execution Session. All ApplicationLogicAbstractions
contains abstraction to don't have direct coupling to UWP Extended Execution Session API. However IExtendedExecutionSessionFactory
has the same limitation as UWP application and allows to create only one Extended Execution Session at one period of time. Attempt to open the second session before disposing the first one will rise InvalidOperationException
. However application logic layer may create some utility to host multiple tasks. Depending on windows battery charge, energy save mode enable, "Battery usage by app" settings and other OS factors, application may call RequestExtensionAsync()
method and get ExtendedExecutionResult.Denied
. It doesn't mean that without extended execution application should not allow user to execution some regular long running context that may be denied or revoked after start. Tampleworks.WindowsApplicationBlock.Demo.ApplicationLogic.ExtendedExecutionTaskAgrigation
class provides such functionality to execute tasks under requested extended execution. Demo application contains sample of Report Generation logic that may be called with required or optional extended execution session. Every demo report generation process may be observer on ViewModel and View layers with notification about changed revoked Extended Execution Session. Please see source code of Tampleworks.WindowsApplicationBlock.Demo.ApplicationLogic.ReportGeneration.ReportGenerationAgent
class.
MSDN contains documentation how to handle MemoryManager.AppMemoryUsageLimitChanging event during background execution. Tampleworks.WindowsApplicationBlock.Demo.ApplicationLogic.ApplicationLogic
class uses injected IApplicationMemoryManager
to monitor AppMemoryUsageLimitChanging
event. Unfortunately it is very difficult to reproduce such event initiated by Windows in background execution. To simulate such process, demo application main window has Simulate button that will run memory pleasure handling logic in 10 seconds when application will be minimized.
To release view layer Application Logic calls IApplicationLogicAgent.ResetViewAsync
method. Applciation Logic may notify all View Models about such unload because view models are provided by Application Logic. All windows visual tree root elements will be set to null
. After that application logic should call GC.Collect
method to release managed objects. It is important to understand that view models provided byt IPageViewModelFactory
will be collected if there will be no references from IWindowFrameController
. Views content will be reconstructed again only when application will move to foreground. To restore views IWindowFrameController will provide IPageViewModelFactory
again. Such lifecycle allows to reconstruct new View Models for opened windows or cache them on IWindowFrameController
level.
IApplicationLogicAgent
interface has OpenNewSecondaryViewAsync
method that allows to open secondary window. OpenNewSecondaryViewAsync
takes IWindowFrameControllerFactory
to build IWindowFrameController
for secondary window. Each window's IWindowFrameController
will have its own IWindowFrameControllerAgent
to invoke View Model methods in window's View dispatcher.