Анатомия приложения и навигация по страницам
Переход между событиями жизненного цикла приложений и состояние сеанса работы
Для приложений и для тех, кто их публикует, идеальным миром был бы мир, в котором пользователи запускают приложения и остаются в них навсегда (и, без сомнения, совершают множество покупок в приложении!). Но в жестокой реальности это невозможно. Независимо от того, как вам хотелось бы, чтобы это было иначе, ваше приложение - не единственное, которое захочется запустить пользователю. В конце концов, зачем были бы нужны функции вроде общего доступа или закрепления контента, если бы несколько приложений не могли исполняться вместе? К лучшему, или к худшему, пользователи будут переключаться между приложениями, менять состояния просмотра, и, возможно, закрывать ваше приложения. Но то, что вы можете сделать - это направить силы в "лучшую" сторону уравнения, убедившись в том, что ваше приложение хорошо ведет себя в любом из этих обстоятельств.
Первое соображение касается фокуса (focus), который применим к элементам управления в вашем приложении и к приложению в целом. Здесь вы можете просто использовать стандартные события HTML blur и focus. Например, в игре в стиле "экшн", или в чём-то подобном, с таймером, постановка на паузу обычно происходит по событию blur, а запуск - по событию focus.
Похожее, но иное условие касается видимости (visibility). Приложение может быть видимым, но не иметь фокуса, как тогда, когда оно работает в прикрепленном режиме. В подобных случаях приложение может продолжать действия, наподобие анимации или обновления ленты новостей, но оно приостановит подобную активность, когда потеряет видимость (то есть, когда оно окажется в фоновом режиме). Для этого используйте событие visibilityChange (http://msdn.microsoft.com/library/windows/apps/hh441213.aspx) из DOM API и затем проверьте свойство visibilityState (http://msdn.microsoft.com/library/windows/apps/hh453385.aspx) объекта window или document, так же, как и свойство document.hidden. (Событие применимо и к видимости отдельных элементов). Изменение видимости, кроме того, отличное время для того, чтобы сохранить пользовательские данные, наподобие документов или состояния прохождения игры.
Приложение может узнать об изменении состояния просмотра (view state change) несколькими способами. Как показано в примере "Here My Am!", приложение обычно использует медиа-запросы (объявленные в CSS или посредством прослушивателей медиа-запросов в коде) для того, чтобы перенастроить макет и видимость элементов, что, на самом деле, единственное, на что должно влиять изменение состояния просмотра. (Опять же, изменение состояния просмотра никогда не изменяет режим работы приложения, только макет и видимость объектов). В любое время приложение может получить сведения о текущем режиме просмотра посредством Windows.UI.ViewManagement.ApplicationView.value. Эта команда возвращает одно из значений типа Windows.UI.ViewManagement.ApplicationViewState, а именно: snapped, filled, fullScreenLandscape и fullScreenPortrait; подробности вы можете найти в "Макет" .
Когда приложение завершает работу (пользователь провёл сверху вниз по экрану или нажал Alt+F4), важно отметить, что сначала приложение попадает во внеэкранный (скрытый) режим, приостанавливается, а потом закрывается, в итоге обычные DOM-события, наподобие unload, не применимы. Пользователь, кроме того, может остановить процесс вашего приложения через Диспетчер задач, однако, при таком стечении обстоятельств, никакие события в коде приложения не генерируются. Помните так же о том, что приложению не следует самому себя закрывать, как говорилось ранее, но оно может воспользоваться командой MSApp.terminateApp для того, чтобы закрыться в ответ на происшествия, последствия которых невозможно исправить.
Приостановка, возобновление, завершение работы приложения
Помимо фокуса, видимости и состояний просмотра есть три других критических момента в жизненном цикле приложения:
- Приостановка (suspending). Когда приложение не видимо в любом режиме просмотра, оно будет приостановлено через пять секунд (если судить по стенным часам) для того, чтобы сэкономить энергию батарей. Это означает, что оно полностью остаётся в памяти, но ему не выделяется время процессора, оно, таким образом, не работает с сетью или с диском (за исключением использования особым образом разрешенных фоновых задач). Когда это происходит, приложение получает событие Windows.UI.WebUI.WebUIApplication.onsuspending, которое так же видимо через WinJS.Application.oncheckpoint. Приложение должно завершить обработку этого события за пять секунд, или Windows решит, что приложение зависло и завершит его (время!). В течение этого времени, приложения сохраняют переходящие во времени состояния сеанса работы и освобождают любые захваченные ранее монопольные (exclusive) ресурсы, такие, как файловые потоки или доступ к устройствам (смотрите материал "Приостановка работы приложения" (http://msdn.microsoft.com/library/windows/apps/hh465138.aspx)).
- Возобновление (resuming). Если пользователь переключается на приостановленное приложение, приложение получает событие Windows.UI.WebUI.WebUIApplication.onresuming. (Оно не отображается посредством WinJS.Application, так как оно обычно не используется и у WinJS нет значения для его добавления). Мы скоро поговорим об этом подробнее в разделе "Данные от сервисов и WinJS.xhr", так как нужда в этом событии часто возникает при использовании сервисов. В дополнение к этому, если вы отслеживаете данные от сенсоров любого рода (вроде компаса, GPS-приёмника или сенсора ориентации в пространстве), возобновление приложения - это подходящее время для того, чтобы обновить эти данные. Кроме того, здесь вы можете проверить статус лицензии приложения и покупок внутри приложения, если вы используете пробную версию приложения или модель с ограничением по времени (смотрите "Макет" курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Кроме того, это время, когда вы можете захотеть обновить макет приложения (как мы увидим в "Макет" ) к иному состоянию просмотра, чем то, в котором оно было приостановлено, так как возможно возобновление работы приложения напрямую в иное состояние просмотра или в другое разрешение экрана, как тогда, когда устройство было подключено к внешнему монитору. То же самое касается активации/деактивации команд работы с буфером обмена.
- Завершение (terminating). Из режима приостановки приложение может быть завершено, если системе нужно больше памяти. Для этого режима нет события, так как по определению приложение уже приостановлено и его код не исполняется. Тем не менее, это важно для жизненного цикла приложения, так как это воздействует на previousExecutionState, когда приложение перезапускается.
Полезно знать, что вы можете симулировать эти состояния в отладчике Visual Studio, используя выпадающее меню панели инструментов, как показано на Рис. 3.7. Эти команды вызывают соответствующие события и установку значения previousExecutionState для следующего запуска приложения. (Отнеситесь с благодарностью к этим элементам управления. Было время, когда у нас их не было, и было очень неудобно отлаживать эти состояния приложения!).
Рис. 3.7. Выпадающее меню в панели инструментов Visual Studio для симуляции приостановки, возобновления и завершения работы приложения
Мы уже вкратце обсудили эти состояния, но давайте посмотрим, как они связаны с запускаемыми событиями и со значением previousExecutionState, которое используется при следующем старте приложения. Это может выглядеть несколько сложным, поэтому переходы между состояниями показаны на Рис. 3.8, а в таблице ниже описано, как определяется значение previousExecutionState.
Значение previousExecutionState | Сценарии |
---|---|
notRunning | Первый запуск после установки из Магазина Windows. Первый запуск после перезагрузки или выхода. Приложение запущено через примерно 10 секунд после того, как было закрыто пользователем (приблизительное время, необходимое для скрытия, приостановки, и полного завершения работы приложения; если пользователь перезапустит приложение быстрее, Windows предварительно немедленно завершит его работу, не завершая операции, выполняемые при приостановке). Приложение было остановлено с помощью Диспетчера задач во время выполнения или самостоятельно закрылось с помощью MSApp.terminateApp. |
running | Приложение уже исполняется и вызвано способом, отличающимся от запуска с помощью плитки, такого, как чудо-кнопки Поиск или Общий доступ, дополнительные плитки, всплывающее уведомление и реализация других контрактов. Когда приложение запущено и пользователь щёлкает по плитке, Windows просто переключается на уже работающее приложение без вызова событий активации (хотя, и focus, и visibilitychange вызываются). |
suspended | Приложение приостановлено и затем вызвано способом, отличающимся от запуска с помощью плитки (как показано выше, для running). В дополнение к событиям focus/visibility, приложение принимает событие resuming. |
terminated | Ранее приложение было приостановлено, после чего завершено Windows по причине нехватки ресурсов. Обратите внимание на то, что это не применимо к MSApp.terminateApp,так как приложение должно выполняться для того, чтобы вызвать данную функцию. |
closedByUser | Приложение было закрыто с помощью не прерванного жеста закрытия (проведение сверху вниз или Alt+F4). "Прерванное" закрытие происходит, когда пользователь возвращается к приложению в течение 10 секунд, в таком случае предыдущее состояние будет установлено в notRunning. |
Активировано, загружено и так далее (activated, load, etc.)
Выполняется (в памяти) (running (in memory))
Приостановка/контрольная точка (suspending/checkpoint)
Приостановлено (в памяти) (suspended (in memory))
Возобновление (resuming)
Нет событий, предыдущее состояние равно значению завершено только если Windows закрыла приложение ((no event) previous state == terminated only if Windows closed the app)
Не исполняется, закрыто пользователем или завершено (notRunning, closedByUser, or terminated)
Основной вопрос для приложения, конечно, не в том, чтобы определить значение previousExecutionState, а в том, что, на самом деле, делать с этим значением при активации. К счастью, эта история немного проще и кое-что мы видим в коде шаблона:
- Если вид активации - это launch и предыдущее состояние - это notRunning или closedByUser, приложению следует запуститься с установками интерфейса по умолчанию и применить любые постоянные настройки и сохраненные состояния. В случае с closedByUser возможны сценарии, когда приложению следует выполнить дополнительные действия (такие, как обновление кэшированных данных) после того, как пользователь явным образом завершит работу приложения и оставит его на какое-то время закрытым.
- Если вид активации - launch и предыдущее состояние - terminated, приложению следует запуститься в том же самом состоянии, в котором оно было перед последней приостановкой.
- При виде активации launch и при других видах активации, которые включают дополнительные аргументы или параметры (как в случае с дополнительными плитками, всплывающими уведомлениями, контрактами), ему следует инициализироваться для обслуживания целей такого запуска, используя дополнительные параметры. Приложение может быть уже запущено, в итоге ему не обязательно инициализировать своё состояние по умолчанию.
Именно по причине наличия второго из вышеперечисленных требований, приложения предоставляют структуру кода для этого случая вместе с обработчиком checkpoint. Мы рассмотрим в подробностях сохранение и перезагрузку состояний в "Быстрый старт" курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Основная идея заключается в том, что приложению следует, когда оно приостанавливается, если не раньше, сохранит любые передаваемые состояния сеанса работы, которые ему нужны для самовосстановления после остановки. Эти состояния включают в себя непереданные данные форм, позицию прокрутки контента, стек навигации по приложению и другие переменные. Это так потому, что хотя Windows может приостановить приложение и выгрузить его из памяти, пользователь всё еще воспринимает приложение работающим. Таким образом, когда пользователи активируют приложение снова для обычного использования (это будет скорее вид активации launch, чем активация посредством контракта), они ожидают, что приложение будет в том же состоянии, в котором было до этого. В течение времени, когда приложение приостанавливается, таким образом, ему нужно сохранить любые состояния, которые могут сделать вышеупомянутое возможным. Приложение затем восстанавливает это состояние, в том случае, если previousExecutionState равно terminated.
Для того, чтобы узнать подробности о том, где это важно при проектировании приложений, обратитесь к материалу "Руководство по приостановке и возобновлению работы приложений" (http://msdn.microsoft.com/library/windows/apps/hh465088.aspx). Ясно то, что если пользователь непосредственно закроет приложение с помощью Alt+F4 или жеста закрытия, будут вызваны события suspending и checkpoint, в итоге приложение сохранит состояния сеанса работы Однако, приложение автоматически завершает работу после приостановки и перезагрузка состояния сеанса работы не будет запрошена при повторном запуске, так как значение previousExecutionState будет notRunning или closedByUser.
Лучше всего, на самом деле, сохранять состояние сеанса работы при его изменении в течение времени выполнения приложения, таким образом, минимизируя объём работы, необходимый для обработки события suspending (где у вас есть всего пять секунд). Помните, что состояние сеанса работы (сессии) не включает данные, которые постоянно присутствуют от сессии к сессии, такие, как файлы пользователя, информация о наивысших достижениях в играх и параметры приложения, так как приложение всегда перезагружает или применяет подобные постоянные данные при каждом способе активации. Единственное, о чём вам стоит беспокоиться - это о поддержании иллюзии того, что приложение всегда запущено.
Вы всегда сохраняете состояние сеанса работы в папку с данными приложения или контейнеры параметров, которые предоставляет API Windows.Storage.ApplicationData (http://msdn.microsoft.com/library/windows/apps/windows.storage.applicationdata.aspx). Подробности об этом будут в "Быстрый старт" курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Сейчас мне хотелось бы лишь указать на некоторые вспомогательные механизмы, которые предоставляет для этих целей WinJS.
Во-первых, это событие WinJS.Application.checkpoint, которое предоставляет единое удобное пространство для сохранения и состояния сеанса работы, и любых других постоянных данных, которые могут у вас быть, если вы еще не сохранили их.
Во-вторых, это объект WinJS.Application.sessionState. При нормальном старте приложения он представляет собой пустой объект, в который вы можете добавить любые свойства по своему желанию, в том числе - другие объекты. Обычный подход заключается в том, чтобы использовать sessionState напрямую, как контейнер для переменных. В событии checkpoint WinJS автоматически сериализует содержимое этого объекта (используя JSON.stringify) в файл, расположенный в папке локальных данных вашего приложения (это значит, что переменные в sessionState должны иметь строковое представление). Обратите внимание на то, что WinJS гарантирует то, что его собственный обработчик события checkpoint всегда вызывается после того, как ваше приложение получит это событие, вы можете быть уверены в том, что WinJS сохранит всё, что вы запишете в sessionState в любое время до того, как сработает команда выхода из вашего обработчика checkpoint.
Затем, когда приложение активируется с предыдущим состоянием - terminated, WinJS автоматически восстанавливает объект sessionState, в итоге, всё что вы в нём разместили снова будет доступным. Если вы использовали этот объект для хранения переменных, вам лишь нужно избегать записи в них значений по умолчанию при перезагрузке состояния сеанса работы.
В-третьих, если вы не хотите использовать объект sessionState, или у вас есть сессия, которая с ним не работает, объект WinJS.Application упрощает запись ваших собственных файлов без необходимости использования асинхронного API WinRT. В особенности, он предоставляет (как показано в документации (http://msdn.microsoft.com/library/windows/apps/br229774.aspx)), объекты local, temp и roaming, каждый из которых имеет методы readText, writeText, exists, и remove. Эти объекты работают с соответствующими им папками данных приложения и предоставляют упрощенное API для операций файлового ввода/вывода, как показано в Сценарии 1, примера "Модель приложения" (http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d).
Последнее вспомогательное средство связано с механизмом отложенного исполнения, похожим на тот, который применяется при активации. Отложенные операции важны, так как Windows приостановит приложение, как только оно завершит обработку события suspending. Если вам нужно откладывание для асинхронных операций, аргументы события WinJS.Application.oncheckpoint предоставляют метод setPromise, который связан с более глубокими механизмами отложенного выполнения WinRT. Как и ранее, вы передаете promise-объект для асинхронной операции (или комбинации операций) методу setPromise, который, в ответ, вызывает отложенный метод complete, когда ожидаемые результаты будут получены.
На уровне WinRT, аргументы события suspending содержат экземпляр Windows.UI.WebUI.- WebUIApplication.SuspendingOperation (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.suspendingoperation.aspx). Он предоставляет метод getDeferral, который возвращает отложенный объект с методом complete, как при активации.
Ух ты! Звучит заманчиво! Может быть, это хитрый способ обойти ограничение на исполнение приложений для Магазина Windows в фоновом режиме? Будет ли приложение исполняться вечно, если я запрошу отсроченную операцию, никогда не вызывая complete?
Не тут-то было, амиго. Примите мои извинения за то, что подарил вам волшебный миг восторга. С отсроченной операцией или нет, пять секунд - это наибольшее время, на которое вы можете рассчитывать. Тем не менее, вы можете в полной мере использовать преимущества этого времени, возможно, сначала выполнив критически важную асинхронную операцию (вроде сбрасывания кэша), и затем попытавшись произвести менее важные действия (вроде синхронизации с сервером), которые могут значительно улучшить опыт взаимодействия пользователя с приложением. Для подобных целей объект suspendingOperation так же содержит свойство deadline, имеющее тип Date, показывающее время в будущем, когда Windows принудительно приостановит приложение вне зависимости от наличия отсроченных операций. Как только первая операция завершится, вы можете проверить, имеется ли время на то, чтобы начать вторую, и так далее.
Базовая демонстрация использования отсроченных операций при приостановке, кстати, сделана в примере "Активация, возобновление работы, приостановка приложения" (http://code.msdn.microsoft.com/windowsapps/App-activating-and-ec15b168). Кроме того, здесь есть пример активации приложения через специальную URI-схему, это мы рассмотрим в "Жизненный путь приложений для Магазина Windows: Характеристики платформы Windows 8" курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Пример управления состояниями приложения, в дополнение к обновлениям, которые мы внесем в нашу программу "Here My Am!" в следующем разделе, можно найти в Сценарии 3 примера "Модель приложения" ((http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d).