Статьи

Windows Phone 8: кратко: основные понятия

В этом руководстве мы рассмотрим некоторые основные концепции разработки Windows Phone, такие как асинхронное программирование, навигация и управление изменениями ориентации. Мы также кратко рассмотрим жизненный цикл типичного приложения для Windows Phone.

В настоящее время асинхронное программирование является обязательным навыком для разработчиков. В прошлом большинство приложений работали с синхронным подходом: пользователь запускал операцию, и, пока она не была завершена, приложение было заморожено и полностью непригодно для использования.

Такое поведение не приемлемо сегодня, особенно в мобильном мире. Пользователи не будут покупать телефон, который не позволяет им отвечать на вызов, потому что приложение застревает, пытаясь завершить операцию.

В Windows Phone существует два подхода к управлению асинхронным программированием: callbacks и шаблон async/await .

В большинстве случаев, особенно в Windows Phone 8, обратные вызовы заменяются асинхронными методами, использующими шаблон асинхронности и ожидания. Однако, есть некоторые API, которые все еще используют подход обратного вызова, поэтому важно изучить его.

Обратные вызовы — это делегированные методы, которые вызываются, когда асинхронная операция завершается. Это означает, что код, который запускает операцию, и код, который управляет результатом, хранятся в двух разных методах.

Давайте рассмотрим следующий пример класса WebClient , одного из основных API-интерфейсов платформы, которая выполняет сетевые операции, такие как загрузка и выгрузка файлов:

01
02
03
04
05
06
07
08
09
10
11
12
private void OnStartDownloadClicked(object sender, RoutedEventArgs e)
{
    WebClient client = new WebClient();
    client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
    client.DownloadStringAsync(new Uri(«http://wp.qmatteoq.com», UriKind.Absolute));
    Debug.WriteLine(«Download has started»);
}
 
void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    MessageBox.Show(e.Result);
}

Загрузка файла (в данном случае мы загружаем HTML веб-страницы) начинается, когда мы вызываем метод DownloadStringAsync() , но нам нужно подписаться на событие DownloadStringCompleted , которое запускается после завершения загрузки, чтобы управлять результат. Обычно обработчик события (метод обратного вызова) имеет параметр, который содержит результат запроса. В предыдущем примере он называется e а его типом является DownloadStringCompletedEventArgs .

Обратите внимание на сообщение «Загрузка началась», которое написано в окне Output Visual Studio. Поскольку метод является асинхронным, сообщение будет отображаться сразу после начала загрузки, поскольку код будет продолжать выполняться до завершения загрузки. Затем выполнение переходит к методу обратного вызова.

Подход обратного вызова трудно читать. В отличие от синхронного кода, поток выполнения «переходит» из одного метода в другой, поэтому разработчик не может просто читать строку за строкой, чтобы понять, что происходит. Кроме того, если вам нужно написать собственный метод, который запускает сетевую операцию и возвращает загруженный файл, вы просто не сможете этого сделать, поскольку логика разделена на две разные части.

Асинхронный и ожидающий шаблон был введен в C # 5.0 для решения этих проблем. Среда выполнения Windows в значительной степени основана на этом подходе, и большинство API-интерфейсов, которые мы увидим в этой серии, используют его.

Когда мы используем ключевые слова async и await , мы собираемся написать последовательный код, как если бы он был синхронным — компилятор будет выполнять одну строку за другой. Чтобы лучше понять, что происходит внутри, имейте в виду, что когда мы запускаем асинхронную операцию, компилятор устанавливает закладку и затем завершает метод. Таким образом, поток пользовательского интерфейса свободен, и пользователи могут продолжать взаимодействовать с приложением. Когда асинхронная операция завершена, компилятор возвращается к закладке и продолжает выполнение.

Шаблон асинхронности и ожидания основан на классе Task , который является базовым типом возврата асинхронного метода. Метод может вернуть:

  • Task если это void метод: компилятор должен дождаться завершения операции для выполнения следующей строки, но не ожидает возврата значения.
  • Task<T> если метод возвращает значение. Компилятор будет ожидать завершения операции и вернет результат (тип которого T ) в метод main, который сможет выполнять над ним дополнительные операции.

Давайте посмотрим на пример. Мы собираемся добавить в наш проект библиотеку Async для .NET , которая была разработана Microsoft и доступна на NuGet . Его цель — добавить асинхронную поддержку и ожидать поддержки старых технологий, которые ее не поддерживают, поскольку они не основаны на C # 5.0, таких как Windows Phone 7 или Silverlight. Это также полезно в приложениях Windows Phone 8, так как добавляет в API асинхронные методы, которые все еще основаны на шаблоне обратного вызова.

Одним из таких API является класс WebClient мы видели ранее. Устанавливая эту библиотеку, мы можем использовать метод DownloadStringTaskAsync() который поддерживает новый шаблон. Тип возвращаемого значения этого метода: Task<string> . Это означает, что операция является асинхронной, и она вернет строку.

1
2
3
4
5
6
private async void OnStartDownloadClicked(object sender, RoutedEventArgs e)
{
    WebClient client = new WebClient();
    string result = await client.DownloadStringTaskAsync(«http://wp.qmatteoq.com»);
    MessageBox.Show(result);
}

Во-первых, давайте отметим, как использовать этот новый синтаксис. Любой метод, содержащий асинхронный метод, должен быть помечен ключевым словом async . В данном случае это обработчик событий, который вызывается, когда пользователь нажимает кнопку.

Далее нам нужно добавить ключевое слово await в качестве префикса асинхронного метода. В нашем примере мы поместили его перед методом DownloadStringTaskAsync() . Это ключевое слово указывает компилятору дождаться завершения метода, прежде чем двигаться дальше.

Когда асинхронный метод запущен, вы можете предположить, что компилятор установил закладку, а затем выйти из метода, потому что приложение все еще реагирует. По завершении загрузки компилятор возвращается к закладке и продолжает выполнение. Результат загрузки сохраняется в переменной result , значение которой отображается на экране с помощью MessageBox .

Как видите, даже если код под капотом является асинхронным и не останавливает пользовательский интерфейс, он выглядит синхронным: код выполняется по одной строке за раз, а MessageBox не отображается, пока не завершится метод DownloadStringTaskAsync() его работа

Иногда, особенно когда вам приходится иметь дело с методами обратного вызова, операции выполняются в фоновом потоке, который отличается от потока, который управляет пользовательским интерфейсом. Этот подход очень помогает поддерживать поток пользовательского интерфейса настолько свободным, насколько это возможно, чтобы интерфейс всегда был быстрым и отзывчивым.

Иногда фоновый поток может потребоваться взаимодействовать с пользовательским интерфейсом. Например, допустим, фоновый поток завершил свою работу и должен отобразить результат в элементе управления TextBlock размещенном на странице. Если вы попытаетесь это сделать, UnauthorizedAccessException ошибка UnauthorizedAccessException с сообщением «Недопустимый межпоточный доступ» будет отображаться.

Причина этой ошибки заключается в том, что фоновый поток не имеет доступа к элементам управления на странице, поскольку они управляются другим потоком. Решение состоит в том, чтобы использовать класс Dispatcher платформы. Его целью является отправка операций в поток пользовательского интерфейса из другого потока. Таким образом, вы можете без проблем взаимодействовать с элементами управления XAML, потому что поток один и тот же. Следующий пример иллюстрирует, как его использовать:

1
2
3
4
Dispatcher.BeginInvoke(() =>
{
    txtResult.Text = «This is the result»;
})

Вам просто нужно передать Action которое необходимо выполнить в потоке пользовательского интерфейса, в качестве параметра метода BeginInvoke() класса Dispatcher . В предыдущем примере Action определено с использованием anonymous method .

Совет: анонимные методы называются такими, потому что они не имеют определенного имени — они просто определяют операции, которые должны быть выполнены.

Dispatcher не требуется при работе с шаблоном async и await, поскольку он гарантирует, что даже если операция выполняется в одном из свободных потоков, доступных в пуле потоков, результат всегда возвращается в потоке пользовательского интерфейса.

Еще одна важная концепция, которую необходимо изучить при разработке приложения для Windows Phone, — это навигация. В большинстве случаев ваше приложение будет состоять из разных страниц, поэтому вам нужен способ перехода с одной страницы на другую.

Фреймворк предлагает встроенную навигационную систему, которой можно управлять с помощью класса NavigationService .

1
2
3
4
private void OnGoToPage2Clicked(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri(«/Pages/Page2.xaml», UriKind.Relative));
}

Система навигации предоставляет на каждой странице два события, которыми можно управлять для перехвата событий навигации: OnNavigatedTo() срабатывает при переходе с другой страницы на текущую; OnNavigateFrom() срабатывает при переходе с текущей страницы на другую. Другое важное событие страницы, на которое можно подписаться, — Loaded , которое запускается, когда страница полностью загружена. Обычно он используется для загрузки данных, которые должны отображаться на странице.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
        Loaded += MainPage_Loaded;
    }
 
    void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Debug.WriteLine(«Page is loaded»);
    }
 
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
    }
 
    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
        base.OnNavigatedFrom(e);
    }
}

Общим требованием при работе с навигацией является передача параметра на другую страницу, например, сценарий мастер / подробности, где вы выбираете элемент списка на главной странице и отображаете его детали на новой странице.

Инфраструктура навигации предлагает встроенный способ передачи простых параметров, таких как строки и числа, которые наследуются непосредственно из веб-мира: параметры строки запроса. Они добавляются непосредственно в URL-адрес страницы, на которую вы хотите перенаправить пользователя, как показано в следующем примере:

1
2
3
4
private void OnGoToPage2Clicked(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri(«/Pages/Page2.xaml?id=1», UriKind.Relative));
}

Вы сможете получить параметр на целевой странице, используя событие OnNavigatedTo и класс NavigationContext , как в следующем примере:

1
2
3
4
5
6
7
8
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    if (NavigationContext.QueryString.ContainsKey(«id»))
    {
        int id = int.Parse(NavigationContext.QueryString[«id»]);
        MessageBox.Show(«The id is » + id);
    }
}

Класс NavigationContext предлагает коллекцию QueryString , которая содержит все доступные параметры. Вы сможете получить значение с помощью его ключа (который является именем параметра).

В некоторых случаях простых параметров недостаточно. Иногда вам нужно передать сложные объекты на другую страницу. Сценарий мастер / детализация также в этом случае является хорошим примером. Во многих ситуациях для правильного заполнения страницы сведений вам необходим доступ ко всему объекту, выбранному в списке.

Хороший подход к этому состоит в том, чтобы передать странице сведений простой параметр, который можно использовать для идентификации выбранного элемента, например уникальный идентификатор. Затем в OnNavigatedTo события OnNavigatedTo загрузите элемент с полученным идентификатором из вашей коллекции данных (которая может быть базой данных или удаленной службой).

Каждое приложение имеет свой собственный стек навигации, который представляет собой список страниц, которые пользователь переместил во время использования приложения. Каждый раз, когда пользователь переходит на новую страницу, он добавляется в стек. Когда пользователь возвращается, последняя страница из стека удаляется. Когда в стеке остается одна страница и пользователь нажимает кнопку «Назад», приложение закрывается.

Класс NavigationService предлагает несколько способов управления стеком. В начале есть специальное свойство BackStack которое представляет собой совокупность всех страниц в стеке. Когда мы обсудим Быстрое Возобновление Приложения позже в этой статье, мы увидим один из доступных методов для работы со стеком: RemoveBackEntry() , который удаляет последнюю страницу из стека.

Другим важным методом является GoBack() , который перенаправляет пользователя на предыдущую страницу в стеке. Его цель — избежать проблем круговой навигации в вашем приложении.

На изображении ниже показан пример круговой навигации и способы ее исправления. Допустим, у вас есть приложение, состоящее из двух страниц: главной страницы и страницы настроек. Вы решаете использовать метод Navigate() класса NavigateService для каждой навигации. Пользователь переходит с главной страницы на страницу настроек и снова на главную страницу.

Проблема в том, что каждый раз, когда вы вызываете метод Navigate() , вы добавляете новую страницу в стек навигации. Когда пользователь находится на главной странице и нажимает кнопку «Назад», он ожидает выхода из приложения. Вместо этого, во второй раз, пользователь будет перенаправлен на страницу настроек, так как предыдущая навигация добавила страницу в стек.

Правильный подход заключается в использовании метода GoBack() . Вместо добавления новой страницы в стек этот метод просто перенаправляет пользователя на предыдущую. Результат будет таким же — пользователь со страницы настроек возвращается на главную страницу, но поскольку в стек не добавлено ни одной страницы, приложение будет работать так, как ожидается. Если пользователь нажимает кнопку «Назад», приложение будет закрыто.

Круговая навигация сверху и правильная навигация снизу

Позже в этой серии мы будем иметь дело с различными ситуациями, когда приложение открывается с помощью специального Uri , например с использованием Speech API или совместного использования данных.

В этих случаях мы будем использовать навигационную функцию под названием UriMapper , которая является классом, который может выступать в качестве посредника в любой операции навигации. Когда пользователь перемещается с одной страницы на другую (начальной страницей также может быть домашний экран телефона), класс может перехватить его и при необходимости перенаправить пользователя на другую страницу.

Чтобы создать класс на основе UriMapper , просто добавьте новый класс в ваш проект. Он должен наследоваться от класса UriMapperBase . Вам потребуется реализовать метод MapUri() , который вызывается во время навигации, как показано в следующем примере:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class UriMapper: UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        string tempUri = HttpUtility.UrlDecode(uri.ToString());
        if (tempUri.Contains(«/FileTypeAssociation»))
        {
            //Manage the selected file.
            return new Uri(«/Pages/FilePage.xaml», UriKind.Relative);
        }
        else
        {
            return uri;
        }
    }
}

Метод MapUri() принимает исходный Uri и должен вернуть новый Uri чтобы перенаправить пользователя. В предыдущем примере мы проверили, содержит ли исходный Uri конкретную строку (в данном случае она относится к сценарию совместного использования данных, который мы увидим позже в этой серии). В утвердительном случае мы перенаправляем пользователя на определенную страницу приложения, которая способна управлять сценарием; в противном случае мы не меняем навигационный поток, возвращая исходный Uri .

После того, как вы создали класс UriMapper , вам нужно будет присвоить его свойству UriMapper объекта RootFrame , объявленного в файле App.xaml.cs Вам нужно будет расширить область, называемую Phone application initialization (которая обычно свернута), и изменить метод InitializePhoneApplication() как в следующем примере:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
private void InitializePhoneApplication()
{
    if (phoneApplicationInitialized)
        return;
 
    // Create the frame but don’t set it as RootVisual yet;
    // splash screen to remain active until the application is ready to render.
    RootFrame = new PhoneApplicationFrame();
    RootFrame.UriMapper = new UriMapper();
    RootFrame.Navigated += CompleteInitializePhoneApplication;
 
    // Handle navigation failures.
    RootFrame.NavigationFailed += RootFrame_NavigationFailed;
 
    // Handle reset requests for clearing the backstack.
    RootFrame.Navigated += CheckForResetNavigation;
 
    // Ensure we don’t initialize again.
    phoneApplicationInitialized = true;
}

Мобильные приложения имеют иной жизненный цикл, чем традиционные настольные приложения, из-за их различных требований, таких как управление питанием и оптимизация производительности. Традиционный подход к многозадачности, который вы испытываете в Windows, не совсем подходит для мобильных устройств: необходимое питание очень быстро разряжает батарею. Кроме того, если вы открываете слишком много приложений, вы можете столкнуться с серьезными проблемами с производительностью, которые ухудшат пользовательский опыт.

На следующем рисунке показан жизненный цикл, введенный Microsoft для приложений Windows Phone для удовлетворения необходимых условий:

Жизненный цикл приложения

Когда приложение запущено, оно может быть приостановлено в любое время. Например, пользователь может нажать кнопку «Пуск» или нажать уведомление, чтобы открыть другое приложение. В этом случае приложение «заморожено» ( dormant ); он сохраняется в памяти, но все запущенные потоки и операции останавливаются. В этом состоянии приложение использует системную память, но процессор свободен. Таким образом, другие приложения могут использовать доступные ресурсы без проблем с производительностью.

Когда пользователь решает вернуться в приложение (с помощью кнопки «Назад» или переключателя задач), его экземпляр восстанавливается из памяти, а предыдущие потоки и операции перезапускаются.

Если пользователи долгое время играют со своим устройством, может открыться слишком много приложений, и не останется достаточно памяти для приостановки других приложений. В этом случае операционная система может начать «убивать» долго работающие приложения, чтобы освободить часть памяти для вновь открытых приложений — старые приложения переводятся в состояние tombstoned . Чтобы предоставить пользователям тот же плавный опыт, что и при простом приостановлении работы приложений, разработчики могут сохранять состояние приложения, когда оно захоронено, и восстанавливать его позже. Когда пользователи открывают надгробное приложение, они находят приложение в точном предыдущем состоянии, как будто оно никогда не закрывалось.

В Windows Phone 7 было доступно только одно захоронение, но оно часто приводило к ухудшению работы пользователей. Поскольку приложение было убито, оно всегда должно запускаться с нуля, что приводит к увеличению времени загрузки. В Windows Phone 7.5 Microsoft представила концепцию быстрого переключения приложений , которая была переведена в текущий жизненный цикл, когда приложения переводятся в состояние покоя и уничтожаются только при необходимости.

Основываясь на этой информации, мы можем понять, что приложения не могут работать в фоновом режиме — когда приложение приостанавливается, все его выполняемые операции отменяются. Позже в этой серии мы рассмотрим доступные методы выполнения некоторых операций, даже когда приложение не запущено.

Как разработчик, вы не будете уведомлены операционной системой, когда приложение захоронено. Вот почему в приведенной выше схеме вы можете видеть, что операция сохранения состояния выполняется каждый раз, когда приложение приостанавливается. Вам всегда придется иметь дело с этим, так как вы не будете заранее знать, будет ли ваше приложение захоронено.

Вместо этого операция восстановления выполняется только в том случае, если приложение было захоронено. Когда приложение находится в состоянии покоя, весь процесс сохраняется в памяти, поэтому при восстановлении приложения не происходит потери данных.

Поскольку захоронение не является детерминированным, Visual Studio предлагает способ его проверки. По умолчанию приложения переводятся в неактивное состояние при приостановке. Вы можете изменить это поведение, щелкнув правой кнопкой мыши по своему проекту и выбрав Свойства . В разделе « Отладка » вы найдете опцию « Надгробная плита при деактивации при отладке» . Когда он выбран, приложения всегда будут захоронены, когда они приостановлены, поэтому вы можете проверить, что все работает нормально и в этом сценарии данные не теряются.

Вы можете контролировать жизненный цикл приложения благодаря классу PhoneApplicationService , который объявлен в файле App.xaml как объект времени жизни.

1
2
3
4
5
<Application.ApplicationLifetimeObjects>
    <shell:PhoneApplicationService
        Launching=»Application_Launching» Closing=»Application_Closing»
        Activated=»Application_Activated» Deactivated=»Application_Deactivated»/>
</Application.ApplicationLifetimeObjects>

Как видите, класс PhoneApplicationService зарегистрирован для управления четырьмя различными событиями, которые доступны в файле App.xaml.cs :

  • Application_Launching вызывается при первом запуске приложения.
  • Application_Closing вызывается, когда приложение полностью закрыто. Это происходит только тогда, когда пользователь нажимает кнопку «Назад» на главной странице приложения.
  • Application_Activated вызывается, когда приложение возобновляется из приостановленного состояния.
  • Application_Deactivated вызывается, когда приложение приостановлено.

Помните операции «сохранить состояние» и «восстановить состояние» на предыдущем рисунке? Обычно они управляются в этих событиях. Состояние сохраняется при возникновении события Application_Deactivated и восстанавливается при возникновении события Application_Activated .

В частности, один из параметров обработчика события Application_Activated содержит важную информацию — он показывает, приходит ли приложение из состояния покоя или захоронения, как показано в следующем примере:

01
02
03
04
05
06
07
08
09
10
11
private void Application_Activated(object sender, ActivatedEventArgs e)
{
    if (e.IsApplicationInstancePreserved)
    {
        Debug.WriteLine(«The app was dormant»);
    }
    else
    {
        Debug.WriteLine(«The app was tombstoned»);
    }
}

Ключом является свойство IsApplicationInstancePreserved объекта ActivatedEventArgs , который является логическим. Если это правда, приложение находилось в состоянии покоя; в противном случае он был убит, и пришло время восстановить ранее сохраненные данные.

Класс PhoneApplicationService также может быть полезен для хранения состояния вашего приложения, когда оно приостановлено. Фактически, он предлагает свойство State , представляющее собой коллекцию, типом которой является Dictionary<string, object> . Его можно использовать для сохранения любого объекта, идентифицируемого ключом.

Как правило, вы собираетесь сохранять данные в свойстве State когда приложение приостановлено, и восстанавливать данные, когда приложение возвращается из состояния захоронения.

Однако есть несколько важных вещей, о которых следует помнить:

  • Лучше всего сохранять данные как можно скорее, а не только при возникновении события Application_Deactivated . Неожиданные ошибки или другие ошибки могут привести к потере данных пользователями во время выполнения.
  • Содержимое свойства State сохраняется в памяти, только если приложение возобновлено; если пользователь решит открыть приложение с нуля (например, нажав на его плитку вместо кнопки «Назад»), коллекция будет стерта, а сохраненные данные будут потеряны. Если вам необходимо сохранить данные в нескольких выполнениях, лучше сохранить их в локальном хранилище, о чем мы расскажем позже в этой серии.

Ранее описанный жизненный цикл имеет недостаток: пользователи могут возобновить приостановленное приложение только с помощью кнопки «Назад» или переключателя задач. Если они прикрепят плитку для приложения на начальном экране и коснутся ее, будет открыт новый экземпляр приложения, а приостановленный будет завершен.

В Windows Phone 8 появилась новая функция под названием « Быстрое возобновление приложения» , которую можно активировать для устранения ранее описанной уязвимости. Независимо от точки открытия, если есть приостановленный экземпляр, он всегда будет использоваться.

Поддержка быстрого возобновления приложения требует небольшой работы, поскольку по умолчанию шаблон Windows Phone создан для использования старого жизненного цикла, даже если эта функция активирована.

Первым шагом является изменение файла манифеста, и, к сожалению, это одна из ситуаций, когда визуальный редактор бесполезен. На самом деле нам нужно вручную отредактировать файл. Для этого просто щелкните правой кнопкой мыши файл WMAppManifest.xml в папке « Свойства » и выберите параметр « Просмотр кода» .

Вы найдете узел с именем DefaultTask который сообщает приложению, какую страницу загружать первой при запуске приложения. Вам нужно будет добавить атрибут ActivationPolicy чтобы установить его значение Resume , как в следующем примере:

1
2
3
<Tasks>
  <DefaultTask Name =»_default» NavigationPage=»MainPage.xaml» ActivationPolicy=»Resume» />
</Tasks>

Что происходит, когда пользователи нажимают на основную плитку приложения и быстрое возобновление приложения включено? Операционная система запускает две навигации: первая — к последней открытой странице в приложении, а вторая — к главной странице приложения, поскольку плитка приложения связана с ней через стандартный Uri навигации.

На данный момент у вас есть два варианта:

  • Вы можете продолжать использовать старый подход. В этом случае вы должны удалить последнюю посещенную страницу из заднего стека; в противном случае вы сломаете навигационную систему. Фактически, когда пользователи находятся на главной странице приложения и нажимают кнопку «Назад», они ожидают выхода из приложения, а не возврата на старую страницу.
  • Вы можете поддержать Быстрое Возобновление Приложения. В этом случае вам необходимо прекратить переход на главную страницу приложения, чтобы пользователи перешли на последнюю посещенную страницу приложения.

Шаблоны Windows Phone 8 по умолчанию содержат реализацию первого подхода. Откройте файл App.xaml.cs и найдите метод InitializePhoneApplication() , который выполняет инициализацию фрейма, управляющего различными страницами и системой навигации. Вы обнаружите, что приложение подписывается на событие с именем Navigated класса RootFrame , которое запускается каждый раз, когда пользователь перемещается с одной страницы приложения на другую.

Метод, назначенный в качестве обработчика этого события, называется ClearBackafterReset() , который имеет следующее определение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private void ClearBackStackAfterReset(object sender, NavigationEventArgs e)
{
    // Unregister the event so it doesn’t get called again.
    RootFrame.Navigated -= ClearBackStackAfterReset;
 
    // Only clear the stack for ‘new’ (forward) and ‘refresh’ navigations.
    if (e.NavigationMode != NavigationMode.New && e.NavigationMode != NavigationMode.Refresh)
        return;
 
    // For UI consistency, clear the entire page stack.
    while (RootFrame.RemoveBackEntry() != null)
    {
        ;
    }
}

Этот метод делает именно то, что было описано ранее: RemoveBackEntry() к главной странице не отменяется, но с помощью RemoveBackEntry() класса RootFrame стек страниц приложения.

Обратите внимание на свойство NavigationMode : оно определяет состояние навигации, например, « New когда пользователи переходят на новую страницу, или « Back когда пользователи возвращаются на предыдущую страницу. Ключевым статусом для управления быстрым возобновлением приложения является « Reset , который устанавливается, когда пользователи переходят на главную страницу приложения, но используется старый экземпляр приложения.

Мы собираемся использовать свойство NavigationMode вместе с некоторыми изменениями в исходном коде. Первым шагом является изменение обработчика события Navigated для простого сохранения в глобальном логическом свойстве класса App.xaml.cs сброса NavigationMode . Мы собираемся использовать эту информацию в другом обработчике событий. Фактически нам нужно подписаться на событие RootFrame класса RootFrame . Это событие похоже на Navigated , за исключением того, что оно вызывается до начала навигации, а не после. Это различие важно для нашей цели, поскольку у нас есть возможность отменить навигационную операцию до ее выполнения.

Вот что мы делаем в обработчике событий Navigating :

1
2
3
4
5
6
7
8
void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
    if (reset && e.IsCancelable && e.Uri.OriginalString == «/MainPage.xaml»)
    {
        e.Cancel = true;
        reset = false;
    }
}

В этом случае мы ожидаем наступления определенного условия: NavigationMode имеет значение « Reset и запускается переход на главную страницу. Эта ситуация возникает, когда пользователи нажимают на основную плитку, а экземпляр приложения уже находится в памяти. Первая навигация перенаправляет на последнюю посещенную страницу, а вторая (режим Reset ) перенаправляет на главную страницу. Именно этим сценарием мы должны управлять. Устанавливая свойство Cancel параметра метода, мы отменяем навигацию к главной странице и оставляем пользователя на последней посещенной странице приложения. Опыт точно такой же, как когда пользователь возвращается в приложение с помощью кнопки «Назад».

Вот как выглядит полный код, необходимый для реализации Fast App Resume, в файле App.xaml.cs :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private bool reset;
 
// Do not add any additional code to this method.
private void InitializePhoneApplication()
{
    if (phoneApplicationInitialized)
        return;
 
    // Create the frame but don’t set it as RootVisual yet;
    // splash screen to remain active until the application is ready to render.
    RootFrame = new PhoneApplicationFrame();
    RootFrame.Navigated += CompleteInitializePhoneApplication;
 
    // Handle navigation failures.
    RootFrame.NavigationFailed += RootFrame_NavigationFailed;
 
    // Handle reset requests for clearing the backstack.
    RootFrame.Navigated += RootFrame_Navigated;
 
    RootFrame.Navigating += RootFrame_Navigating;
 
    // Ensure we don’t initialize again.
    phoneApplicationInitialized = true;
}
 
void RootFrame_Navigated(object sender, NavigationEventArgs e)
{
    reset = e.NavigationMode == NavigationMode.Reset;
}
 
void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
    if (reset && e.IsCancelable && e.Uri.OriginalString == «/MainPage.xaml»)
    {
        e.Cancel = true;
        reset = false;
    }
}

Вы можете быть удивлены, почему все эти усилия необходимы для поддержки Fast App Resume. Почему он не реализуется автоматически операционной системой? Ответ в том, что Fast App Resume подходит не для каждого приложения — вы должны быть очень осторожны, когда решите поддержать его, потому что вы можете испортить пользовательский опыт, если не будете его правильно реализовывать.

Например, если ваши пользователи проводят большую часть времени на главной странице вашего приложения (например, в приложении для социальных сетей), они, вероятно, предпочитают переходить прямо на домашнюю страницу при открытии приложения, а не возобновлять работу в прежнем состоянии. делаю раньше.

Вот два совета по улучшению опыта быстрого восстановления приложения:

  • Если в вашем приложении много страниц и сложная навигационная иерархия, добавьте к внутренним страницам быстрый способ вернуться на домашнюю страницу (например, кнопку на панели приложений).
  • Добавьте условие времени. Если приложение недавно не использовалось, отключите быстрое возобновление работы приложения и перенаправьте пользователя на домашнюю страницу.

Как правило, устройство Windows Phone используется в портретном режиме, но есть некоторые сценарии, в которых пользователи могут извлечь выгоду из ландшафтного режима. Например, если ваше приложение предлагает способ вставки длинного текста, пользователям будет удобнее набирать текст с помощью альбомной клавиатуры. Кроме того, в других сценариях вы можете использовать альбомный режим, чтобы обеспечить контент с другим макетом.

По умолчанию каждая новая страница, добавленная в Windows Phone, отображается в портретном режиме и не поддерживает альбомный режим. При повороте телефона ничего не происходит. Поведение ориентации контролируется двумя атрибутами узла PhoneApplicationPage в XAML:

1
2
3
<phone:PhoneApplicationPage
   x:Class=»Webinar.Rest.MainPage»
   SupportedOrientations=»Portrait» Orientation=»Portrait»>
  • Свойство SupportedOrientations определяет, какие ориентации поддерживаются страницей. Для поддержки обеих ориентаций вам необходимо установить значение PortraitOrLandscape .
  • Свойство Orientation определяет ориентацию страницы по умолчанию.

После того, как вы установили свойство SupportedOrientations в PortraitOrLandscape , макет страницы автоматически адаптируется к ландшафтному режиму при повороте телефона.

Если вам нужен дополнительный контроль (например, потому что вы хотите глубоко изменить макет страницы между портретным и альбомным режимами), API предлагают событие страницы под названием OrientationChanged которое вызывается каждый раз, когда телефон поворачивается из портретного в альбомный режим и наоборот. , Событие предоставляется непосредственно классом PhoneApplicationPage , поэтому вы можете подписать его в XAML, как PhoneApplicationPage в следующем примере:

1
2
3
4
<phone:PhoneApplicationPage
   x:Class=»Webinar.Rest.MainPage»
   SupportedOrientations=»Portrait» Orientation=»Portrait»
   OrientationChanged=»MainPage_OnOrientationChanged»>

Обработчик событий, который будет сгенерирован в коде, вернет информацию о новой ориентации. Вы можете использовать его для перемещения или изменения аспекта элементов управления, размещенных на странице. В следующем примере показано, как установить другой элемент управления « Height Button соответствии с обнаруженной ориентацией:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private void MainPage_OnOrientationChanged(object sender, OrientationChangedEventArgs e)
{
    if (e.Orientation == PageOrientation.PortraitUp || e.Orientation == PageOrientation.PortraitDown)
    {
        Button1.Height = 100;
    }
    else
    {
        if (e.Orientation == PageOrientation.LandscapeLeft || e.Orientation == PageOrientation.LandscapeRight)
        {
            Button1.Height = 500;
        }
    }
}

Если вы новичок в разработке для Windows Phone, эта статья очень важна, потому что мы изучили некоторые базовые основные понятия, которые мы собираемся использовать снова в последующих выпусках этой серии.

Вот краткий обзор:

  • Асинхронное программирование — обязательное знание для мобильных разработчиков. Мы узнали, как обратные вызовы и новый шаблон асинхронности и ожидания помогут вам поддерживать быстрый и отзывчивый пользовательский интерфейс.
  • Маловероятно, что ваше приложение состоит из одной страницы, поэтому мы узнали, как поддерживать навигацию между различными страницами и как правильно управлять потоком навигации.
  • Жизненный цикл приложения — еще одна ключевая концепция. Вы должны понять, как работает приложение Windows Phone для правильного управления всеми сценариями многозадачности. Мы поговорили о быстрой смене приложений, улучшении, появившемся в Windows Phone 7.5 для ускорения процесса приостановки и возобновления, и Fast App Resume, новой функции Window Phone 8, которая улучшает процесс возобновления.

Это руководство представляет собой главу из Windows Phone 8 Succinctly , бесплатной электронной книги от команды Syncfusion.