Статьи

Prism и универсальное приложение для Windows — управление жизненным циклом приложения

Если это не ваш первый опыт разработки приложений для Магазина Windows, вы должны быть знакомы с концепцией «жизненного цикла приложения». Приложения для Магазина Windows создаются для сценариев, в которых время автономной работы, памяти и ЦП не ограничено: следовательно, старый настольный подход, при котором приложения могут работать в фоновом режиме в течение неопределенного времени, плохо подходит для этого нового современного мира, где ПК больше не являются единственным устройством, используемым для подключения к Интернету, работы, игр и т. Д.

Когда Магазин Windows больше не находится на переднем плане (поскольку пользователь вернулся на начальный экран, он запустил другое приложение, нажал на всплывающее уведомление и т. Д.), Он приостанавливается через 10 секунд: процесс сохраняется в памяти , но все выполняющиеся операции (сетевые подключения, потоки и т. д.) останавливаются. Приложение продолжит использовать оперативную память, но не сможет «украсть» мощность процессора, сети и т. Д. Для других приложений. Таким образом, любое другое приложение сможет использовать те же ресурсы, что и ранее открытые. Тем не менее, объем оперативной памяти не бесконечен: следовательно, когда она заканчивается, операционная система может завершать старые приложения, чтобы освободить часть памяти.

Однако это завершение должно быть прозрачным для пользователя: поскольку он явно не закрывал приложение (например, с помощью переключателя задач в Windows Phone или путем перетаскивания приложения сверху вниз в Windows), он ожидает найти его в том же состоянии, которое он оставил. Это то, что мы, как разработчики, называем «управлением состоянием приложения»: обычно, когда приложение приостанавливается, мы собираемся сохранить в локальном хранилище (содержимое которого сохраняется в разных случаях) все данные, которые нам нужны. Создайте впечатление, что приложение никогда не закрывалось (последняя открытая страница, содержимое формы и т. д.). Когда приложение запускается, если оно было прервано из-за нехватки памяти, нам нужно загрузить это состояние и восстановить приложение в предыдущем состоянии.

В типичном приложении Universal для Windows мы выполняем эту операцию в два этапа:

  1. Первый — использовать события OnNavigatedTo () и OnNavigatedFrom () , предоставляемые страницей, для сохранения и восстановления состояния страницы.
  2. Второй — использовать различные методы, предлагаемые классом 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, все еще будут там. Если вы запустите приложение с нуля, вместо этого два элемента управления будут пустыми: это правильно, потому что в этом случае пользователь запускает приложение в первый раз или после того, как он явно закрыл его, поэтому он не ожидает его нахождения. в предыдущем состоянии.

4,11

Если вы хотите убедиться, что это не уловка, но 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.