Если это не ваш первый опыт разработки приложений для Магазина Windows, вы должны быть знакомы с концепцией «жизненного цикла приложения». Приложения для Магазина Windows создаются для сценариев, в которых время автономной работы, памяти и ЦП не ограничено: следовательно, старый настольный подход, при котором приложения могут работать в фоновом режиме в течение неопределенного времени, плохо подходит для этого нового современного мира, где ПК больше не являются единственным устройством, используемым для подключения к Интернету, работы, игр и т. Д.
Когда Магазин Windows больше не находится на переднем плане (поскольку пользователь вернулся на начальный экран, он запустил другое приложение, нажал на всплывающее уведомление и т. Д.), Он приостанавливается через 10 секунд: процесс сохраняется в памяти , но все выполняющиеся операции (сетевые подключения, потоки и т. д.) останавливаются. Приложение продолжит использовать оперативную память, но не сможет «украсть» мощность процессора, сети и т. Д. Для других приложений. Таким образом, любое другое приложение сможет использовать те же ресурсы, что и ранее открытые. Тем не менее, объем оперативной памяти не бесконечен: следовательно, когда она заканчивается, операционная система может завершать старые приложения, чтобы освободить часть памяти.
Однако это завершение должно быть прозрачным для пользователя: поскольку он явно не закрывал приложение (например, с помощью переключателя задач в Windows Phone или путем перетаскивания приложения сверху вниз в Windows), он ожидает найти его в том же состоянии, которое он оставил. Это то, что мы, как разработчики, называем «управлением состоянием приложения»: обычно, когда приложение приостанавливается, мы собираемся сохранить в локальном хранилище (содержимое которого сохраняется в разных случаях) все данные, которые нам нужны. Создайте впечатление, что приложение никогда не закрывалось (последняя открытая страница, содержимое формы и т. д.). Когда приложение запускается, если оно было прервано из-за нехватки памяти, нам нужно загрузить это состояние и восстановить приложение в предыдущем состоянии.
В типичном приложении Universal для Windows мы выполняем эту операцию в два этапа:
- Первый — использовать события OnNavigatedTo () и OnNavigatedFrom () , предоставляемые страницей, для сохранения и восстановления состояния страницы.
- Второй — использовать различные методы, предлагаемые классом App, для управления жизненным циклом приложения, такие как OnSuspended () (для сохранения состояния) и OnLaunched () (для восстановления состояния в случае, если мы обнаружим, что приложение было прекращено с помощью операционная система).
Однако Prism предлагает более простой способ управления этим сценарием. Давайте посмотрим на детали.
Сохранение простых данных
Самый простой сценарий — это когда нам нужно сохранить простые данные, такие как текст в TextBox или логическое значение в CheckBox . Подобные свойства могут автоматически сохраняться Prism, когда приложение приостанавливается, и восстанавливаться, когда оно активируется, просто пометив их атрибутом RestorableState . Допустим, у вас есть страница со следующим XAML:
<StackPanel Margin="12, 0, 12, 0"> <TextBox Text="{Binding Path=Name, Mode=TwoWay" /> <TextBox Text="{Binding Path=Surname, Mode=TwoWay}" Margin="0, 0, 0, 20 "/> </StackPanel>
Мы добавили два элемента управления TextBox , которые находятся в привязке (в двухстороннем режиме) с двумя свойствами в ViewModel, которые называются Name и Surname . Давайте посмотрим, как они определены:
public class MainPageViewModel : ViewModel { private string _name; [RestorableState] public string Name { get {return _name;} set { SetProperty(ref _name, value); } } private string _surname; [RestorableState] public string Surname { get { return _surname; } set { SetProperty(ref _surname, value); } } }
Вы можете заметить, что мы имеем дело с двумя стандартными свойствами, которые благодаря методу SetProperty (), предлагаемому Prism, могут уведомлять View каждый раз, когда их значение изменяется. Однако вы также можете заметить, что мы украсили публичные свойства атрибутом [RestorableState] . Этого достаточно для включения автоматического управления состоянием с помощью Prism.
Проверить сценарий легко благодаря инструментам, предоставляемым Visual Studio: запустите приложение с подключенным отладчиком и напишите несколько текстов в два элемента управления TextBox . Когда сеанс отладки запущен, вы найдете раскрывающееся меню на панели инструментов местоположения отладки (если вы его не видите, просто щелкните правой кнопкой мыши в пустом месте в верхней области и включите его) под названием « События жизненного цикла» . В этом раскрывающемся списке представлен список параметров для имитации состояния другого приложения, поскольку некоторые из них не являются детерминированными: одним из них является завершение, поскольку мы не знаем, когда и когда операционная система завершит работу нашего приложения из-за нехватки памяти. Выберите Приостановить и выключитьиз меню: приложение будет остановлено, а отладчик отключен. Теперь снова запустите приложение: вы заметите, что, несмотря на то, что процесс был завершен, два значения, которые вы вставили в элементы управления TextBox, все еще будут там. Если вы запустите приложение с нуля, вместо этого два элемента управления будут пустыми: это правильно, потому что в этом случае пользователь запускает приложение в первый раз или после того, как он явно закрыл его, поэтому он не ожидает его нахождения. в предыдущем состоянии.
Если вы хотите убедиться, что это не уловка, но Prism действительно управляет состоянием для вас, просто попробуйте удалить атрибуты [RestorableState] из одного из двух свойств: если вы снова смоделируете завершение, вы заметите, что только свойство, которое все еще помечено атрибутом, восстановит его значение, тогда как другой TextBox будет пустым.
Сохранение сложных данных
Другой распространенный сценарий — это когда вам приходится иметь дело со сложными данными, такими как классы, которые являются частью вашей модели. Скажем, например, что свойства Name и Surname, которые мы видели ранее, составляют класс с именем Person со следующим определением:
public class Person { public string Name { get; set; } public string Surname { get; set; } }
Теперь давайте изменим XAML нашей страницы следующим образом:
<storeApps:VisualStateAwarePage x:Class="Prism_StateManagement.Views.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Prism_StateManagement.Views" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps" xmlns:mvvm="using:Microsoft.Practices.Prism.Mvvm" mc:Ignorable="d" mvvm:ViewModelLocator.AutoWireViewModel="True" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <StackPanel Margin="12, 0, 12, 0"> <TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <TextBox Text="{Binding Path=Surname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0, 0, 0, 20 "/> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=LatestPerson.Name}" Style="{StaticResource HeaderTextBlockStyle}" /> <TextBlock Text="{Binding Path=LatestPerson.Surname}" Style="{StaticResource HeaderTextBlockStyle}" Margin="30, 0, 0, 0" /> </StackPanel> </StackPanel> </Grid> <Page.BottomAppBar> <CommandBar> <CommandBar.PrimaryCommands> <AppBarButton Label="Ok" Icon="Accept" Command="{Binding Path=ShowMessageCommand}" /> </CommandBar.PrimaryCommands> </CommandBar> </Page.BottomAppBar> </storeApps:VisualStateAwarePage>
Мы добавили пару новых элементов управления TextBlock , которые связаны со свойством в ViewModel с именем LatestPerson : используя точку в качестве разделителя, мы получаем доступ к свойствам имени и фамилии класса. Мы также добавили кнопку на панель приложения, которая связана с командой во ViewModel с именем ShowMessageCommand. Давайте теперь посмотрим, новое определение ViewModel:
public class MainPageViewModel : ViewModel { private string _name; [RestorableState] public string Name { get {return _name;} set { SetProperty(ref _name, value); } } private string _surname; [RestorableState] public string Surname { get { return _surname; } set { SetProperty(ref _surname, value); } } private Person _latestPerson; public Person LatestPerson { get {return _latestPerson;} set { SetProperty(ref _latestPerson, value); } } public MainPageViewModel() { ShowMessageCommand = new DelegateCommand(() => { LatestPerson = new Person { Name = Name, Surname = Surname }; }); } public DelegateCommand ShowMessageCommand { get; private set; }
Мы добавили новое свойство Person с именем LatestPerson , которое связывается с новыми элементами управления TextBlock, которые мы добавили на страницу. Мы также определили новый DelegateCommand с именем ShowMessageCommand , который срабатывает при нажатии кнопки на панели приложения: команда просто берет значения, вставленные пользователем в два элемента управления TextBox на странице, и использует их для создания новый объект Person , который отображается на странице, просто назначив его свойству LatestPerson .
Теперь предположим, что мы хотим сохранить также значение свойства LatestPerson, чтобы при восстановлении приложения пользователем, даже если оно было прекращено, элементы управления TextBox и новые элементы TextBlock будут содержать предыдущее значение. В этом случае мы не можем просто добавить атрибут [RestorableState] в свойство LatestPerson , поскольку он сложный. Нам нужно использовать другой подход, благодаря другому помощнику, предлагаемому Prism: классу под названием SessionStateManager . Как и NavigationService , этот класс зарегистрирован в контейнере Unity в классе App внутри метода OnInitializeAsync ():
protected override Task OnInitializeAsync(IActivatedEventArgs args) { // Register MvvmAppBase services with the container so that view models can take dependencies on them _container.RegisterInstance<ISessionStateService>(SessionStateService); _container.RegisterInstance<INavigationService>(NavigationService); // Register any app specific types with the container // Set a factory for the ViewModelLocator to use the container to construct view models so their // dependencies get injected by the container ViewModelLocationProvider.SetDefaultViewModelFactory((viewModelType) => _container.Resolve(viewModelType)); return Task.FromResult<object>(null); }
Благодаря подходу внедрения зависимостей вы сможете использовать класс SessioneStateService, просто добавив параметр ISessionStateService в конструктор ViewModel, как в следующем примере:
public class MainPageViewModel : ViewModel { private readonly ISessionStateService _sessionStateService; public MainPageViewModel(ISessionStateService sessionStateService) { _sessionStateService = sessionStateService; } }
Класс SessionStateService предлагает свойство SessionState , типом которого является Dictionary <string, object>: вы сможете сохранять в этой коллекции любые типы сложных данных, которые вы хотите сохранить в случае завершения. Под капотом содержимое коллекции будет сериализовано в хранилище. Класс SessionStateService очень полезен, потому что он заботится об автоматическом сохранении и восстановлении его содержимого, когда приложение приостанавливается и возобновляется: как разработчики, мы должны просто предпринять, чтобы сохранить внутри SessionStateСбор данных, которые мы хотим сохранить. Вот и все: Prism позаботится о том, чтобы сохранить его, когда приложение приостановлено, и восстановить его в случае, если приложение было возобновлено и оно было прекращено операционной системой.
Поскольку это стандартная коллекция Dictonary , работать с ней очень просто. Вот полная ViewModel:
public class MainPageViewModel : ViewModel { private readonly ISessionStateService _sessionStateService; private string _name; [RestorableState] public string Name { get {return _name;} set { SetProperty(ref _name, value); } } private string _surname; [RestorableState] public string Surname { get { return _surname; } set { SetProperty(ref _surname, value); } } private Person _latestPerson; public Person LatestPerson { get {return _latestPerson;} set { SetProperty(ref _latestPerson, value); } } public MainPageViewModel(ISessionStateService sessionStateService) { _sessionStateService = sessionStateService; ShowMessageCommand = new DelegateCommand(() => { LatestPerson = new Person { Name = Name, Surname = Surname }; if (sessionStateService.SessionState.ContainsKey("Person")) { sessionStateService.SessionState.Remove("Person"); } sessionStateService.SessionState.Add("Person", LatestPerson); }); } public DelegateCommand ShowMessageCommand { get; private set; } public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState) { base.OnNavigatedTo(navigationParameter, navigationMode, viewModelState); if (_sessionStateService.SessionState.ContainsKey("Person")) { LatestPerson = _sessionStateService.SessionState["Person"] as Person; } } }
Когда ShowMessageCommand выполняется, кроме простого присвоения значения свойству LatestPerson , мы сохраняем его в коллекции SessionState , просто используя метод Add () . Затем в методе OnNavigatedTo () (который мы обсуждали в предыдущем посте и он срабатывает, когда пользователь переходит на текущую страницу, также в случае возобновления), мы можем проверить, содержит ли SessionState значение, которое мы ранее сохранено, что идентифицируется ключом Person . Если он существует, мы извлекаем его и назначаем его свойству LatestPerson после выполнения приведения с момента SessionState Коллекция содержит общие объекты.
Однако, если вы попытаетесь выполнить приложение, вы получите исключение, когда приложение приостановлено: это происходит потому, что Person — это пользовательский класс, он не является частью среды выполнения Windows, поэтому Prism не знает, как с ним работать это когда дело доходит до сохранения состояния путем его сериализации. Мы можем решить эту проблему, переопределив метод в классе App с именем OnRegisterKnownTypesForSerialization (), в котором мы должны зарегистрировать в SessionStateService каждый пользовательский класс, который мы собираемся использовать в приложении, как в следующем примере:
protected override void OnRegisterKnownTypesForSerialization() { base.OnRegisterKnownTypesForSerialization(); SessionStateService.RegisterKnownType(typeof(Person)); }
Мы просто вызываем метод RegisterKnownType () , передавая в качестве параметра тип класса (в данном случае Person ).
Вот и все! Теперь, если вы запустите приложение и, опять же, имитируя завершение с помощью параметра « Приостановить и выключить» в Visual Studio, вы заметите, что мы будем использовать как простые свойства ( Имя и Фамилия ), так и сложные ( LatestPerson ) правильно восстановлено.
Завершение
Как обычно, вы можете найти пример проекта, использованного для этого поста, на GitHub по адресу https://github.com/qmatteoq/Prism-UniversalSample.