Если это не ваш первый опыт разработки приложений для Магазина 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.
