Опубликован: 02.04.2013 | Доступ: свободный | Студентов: 1117 / 53 | Длительность: 20:18:00
Специальности: Программист
Лекция 4:

Анатомия приложения и навигация по страницам

Базовое управление состояниями в "Here My Am!"

Для того, чтобы показать основные приемы обработки состояний сеанса приложения, я внес некоторые изменения в "Here My Am!". Их можно найти в упражнении HereMyAm3c. У нас есть два набора данных, о которых мы беспокоимся: переменные lastCapture (объект StorageFile с изображением) и lastPosition (набор координат). Мы хотим быть уверенными в том, что сохранили их при приостановке приложения, в итоге, мы сможем подходящим образом использовать эти значения, когда приложение будет запущено с предыдущим состоянием terminated.

В случае с lastPosition мы можем просто поместить эти данные в объект sessionState (вставив app.sessionState.) в обработчик завершения getGeoPositionAsync:

gl.getGeopositionAsync().done(function (position) {
app.sessionState.lastPosition = {	
latitude: position.coordinate.latitude,	
e
longitude: position.coordinate.longitud	
};	
updatePosition();	
}, function (error) {	
console.log("Unable to get location.");
});	
}

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

function updatePosition() {	
if (!app.sessionState.lastPosition) {
return;	
}	
callFrameScript(document.frames["map"], "pinLocation", [app.sessionState.lastPosition.latitude, app.sessionState.lastPosition.longitude]);
}

Отметим так же, что app.sessionstate инициализируется по умолчанию пустым объектом, {}, поэтому lastPosition будет иметь значение undefined до определения местоположения. Это полезно нам при восстановлении состояния приложения. Вот как могут выглядеть, с учетом этого, условия с previousExecutionState:

if (args.detail.previousExecutionState !==	
activation.ApplicationExecutionState.terminated) {	
//Нормальный старт: инициализируем lastPosition с помощью API
//определения местоположения	
} else {	
//WinJS перезагружает здесь объект sessionState. Поэтому попытаемся отобразить на карте 
//местоположение из сохраненных данных.
updatePosition();	
}

Так как мы храним lastPosition в sessionState, эти данные автоматически сохраняются в WinJS.Application.checkpoint, когда приложение было запущено ранее. Когда мы перезапускаемся из состояния terminated, WinJS автоматически перезагружает sessionState; если ранее мы сохранили здесь данные, они будут загружены и updatePosition просто будет работать.

Вы можете проверить это, запустив приложение с данными изменениями и затем использовав команду Приостановить и завершить работу (Suspend and shutdown) на панели инструментов Visual Studio. Установите точку останова на вызов updatePosition выше и затем перезапустите приложение в отладчике. Вы увидите, что в этот момент sessionState.lastPosition уже инициализировано.

В случае с последним захваченным изображением, нам не нужно сохранять объект типа StorageFile, нам нужно сохранить лишь путь к файлу: мы копируем файл в папку локальных данных приложения (в итоге, файл сохраняется между сеансами работы) и можем просто использовать схему URI ms-appdata:// для того, чтобы сослаться на него. Когда мы захватываем изображение, мы просто сохраняем этот URI в sessionState.imageURL (имя свойства может быть произвольным) в конце цепочки promise-вызовов внутри capturePhoto:

app.sessionState.imageURL = "ms-appdata:///local/HereMyAm/" + newFile.name;
that.src = app.sessionState.imageURL

Это значение так же будет перезагружено в соответствующем месте при перезагрузке, в итоге, мы можем просто инициализировать соответствующим образом img src:

if (app.sessionState.imageURL) {
document.getElementById("photo").src = app.sessionState.imageURL;
}

Этот код инициализирует отображаемое изображение из sessionState, но нам, кроме того, нужно инициализировать lastCapture для того, чтобы то же самое изображение было доступно для контракта Общий доступ. Для этого нам нужно, кроме того, сохранить полный путь к файлу, в итоге, мы можем повторно получить объект типа StorageFile посредством Windows.SttorageFile.getFileFromPathAsync (что не работает с URI ms-appdata://). В итоге, в capturePhoto:

app.sessionState.imagePath = newFile.path;

И при загрузке:

if (app.sessionState.imagePath) {	
Windows.Storage.StorageFile.getFileFromPathAsync(app.sessionState.imagePath)
.done(function (file) {	
lastCapture = file;	
if (app.sessionState.imageURL) {	
document.getElementById("photo").src = app.sessionState.imageURL;
}	
});	

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

При всём этом, отметим снова, что нам не нужно явно перезагружать эти переменные внутри ветви кода, соответствующей состоянию terminated, так как WinJS перезагружает sessionState автоматически. Если бы мы управляли состоянием приложения напрямую, например, сохраняли бы некоторые переменные среди перемещаемых параметров внутри события checkpoint, мы бы перезагрузили и применили их значения в это время.

Примечание. Использование ms-appdata:/// и getFileFromPathAsync возможно, так как файл находится в месте, куда мы можем получить программный доступ по умолчанию. Это, кроме того, работает для библиотек, которые мы указали среди возможностей в манифесте. Если, однако, мы получили объект StorageFile после работы со средством выбора файла, мы должны сохранить его в Windows.Storage.AccessCashe для того, чтобы сохранить права доступа между сеансами работы с приложением.

Данные от сервисов и WinJS.xhr

Хотя мы видели примеры использования данных из пакета приложения (с помощью URI или Windows.ApplicationModel.Package.current.installedLocation), так же, как и из папок данных приложения, весьма вероятно, что ваше приложение будет включать в себя данные из веб-сервисов, и, возможно, так же отправлять данные в эти сервисы. Наиболее распространённым для этих целей методом является XmlHttpRequest. Вы можете использовать его в исходной (асинхронной) форме, если хотите, или вы можете предохранить себя от множества сложностей, используя функцию WinJS.xhr, которая удобно оборачивает все эти действия в promise-объекты.

Сделать запрос очень просто, как показано в примере SimpleXhr для этой лекции. Здесь мы используем WinJS.xhr для получения RSS-канала с блога разработчиков Windows 8:

WinJS.xhr({ url: "http://blogs.msdn.com/b/windowsappdev/rss.aspx" })
.done(processPosts, processError, showProgress);

То есть, здесь мы предоставляем WinJS.Xhr URI и получаем promise-объект, который доставляет свой результат в наш обработчик завершения (в данном случае это processPosts) и даже вызывает индикатор прогресса, если он предоставлен ему. В предыдущем случае результат содержит свойство responseXML, которое является объектом DomParser. В случае с последним, объект события содержит текущий XML в его свойстве response, который мы можем просто использовать для показа объема загруженных данных:

function showProgress(e) {	
var bytes = Math.floor(e.response.length / 1024);	
document.getElementById("status").innerText = "Downloaded " + bytes + " KB";
}

В остальном, приложение просто перерабатывает полученный текст в поисках элементов item и отображает поля title, pubDate и link. Она применяет некоторое форматирование (смотрите файл default.css) и использует классы типографического стиля из WinJS, в частности, win-type-x-large (для title), win-type-medium (для pubDate), и win-type-small (для link). В итоге, мы быстро разработали приложение, которое выглядит так, как показано на Рис. 3.9. Вы можете взглянуть на код для того, чтобы увидеть подробности7В WinRT есть специальное API для работы с RSS-каналами в Windows.Web.Syndication. Вы можете использовать его, если хотите лучше структурировать подобные источники данных. Так же, JavaScript имеет внутренние API для работы с XML, в итоге, вы можете пользоваться тем, что кажется вам более подходящим. В случае вроде этого, API синдикации вместе с Windows.Web.AtomPub и Windows.Data.Xml сильнее нужны приложениям для Windows 8, написанным на других языках, которые не имеют тех же встроенных возможностей, что и JavaScript..

Вывод данных в приложении SimpleXhr

увеличить изображение
Рис. 3.9. Вывод данных в приложении SimpleXhr

Для полной демонстрации XHR и связанных вопросов обратитесь к примеру "XHR, обработка ошибок навигации и URL-схем" (http://code.msdn.microsoft.com/windowsapps/XHR-handling-navigation-50d03a7a) и к руководству: "Создание гибридного веб-приложения" (http://msdn.microsoft.com/library/windows/apps/hh452745.aspx). Я не вхожу в описание подробностей о XHR в данном учебном курсе, так как это, в основном, вопрос получения и обработки данных, что имеет мало общего с платформой Windows 8. Что нас интересует, так это последствия приостановки и возобновления работы приложения.

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

В первом случае, данные приостановленного приложения находятся в памяти. Очень нужно решить, поэтому, устарели ли данные с момента приостановки приложения и истекли ли тайм-ауты соединений с серверами. Так же вы можете подумать о том, после какого периода времени пользователь не помнит и не забоится о том, что произошло, когда он в последний раз видел приложение? Если это неделя или больше, может быть разумным перезапустить приложение или восстановить его состояние по умолчанию. Далее, если вы приводите приложение в состояние, точно соответствующее тому, в котором оно было, пользователь испытывает уверенность, что он может оставить приложение работать сколько угодно долго и ничего не потерять. Или вы можете пойти на компромисс и дать пользователю возможность настроить поведение приложения. Конечно, вы будете размышлять над собственным сценарием, однако, если сомневаетесь, перезагрузите состояние приложения, которое было оставлено пользователем на какое-то время.

Для того, чтобы проверить, сколько времени прошло, сохраните отметку времени при приостановке приложения (с помощью new Date().getTime()), получите другую отметку времени при обработке события resuming, найдите разницу и сравните её с заданным вами периодом обновления. Приложение Stock (Акции), например, может иметь очень короткий период. В случае с приложением для чтения блога разработчиков Windows 8, с другой стороны, новые записи появляются не чаще раза в день, поэтому здесь, для того, чтобы поддерживать приложение в актуальном состоянии и получать новые записи, подойдёт более длительный период, измеряемый часами.

Это реализовано в SimpleXhr путём размещения вызовов WinJS.hxr в отдельной функции, названной downloadPosts, которая вызывается при запуске приложения. Затем мы регистрируемся для обработки события resuming в WinRT:

Windows.UI.WebUI.WebUIApplication.onresuming = function () {
app.queueEvent({ type: "resuming" });
}

Помните, я говорил о том, что мы могли бы использовать WinJS.Application.queueEvent для того, чтобы вызывать собственные события объекта приложения? Вот отличный пример. WinJS.Application не включает в себя автоматически события resuming, так как ему нечего добавить к этому процессу. Но приведенный ниже код реализует в точности то же самое, позволяя нам регистрировать прослушиватели событий, наряду с другими событиями наподобие checkpoint:

app.oncheckpoint = function (args) {	
//Сохраняем в sessionState в том случае, если хотим использовать это с кэшированием
app.sessionState.suspendTime = new Date().getTime();	
};	

app.addEventListener("resuming", function (args) {
//Обычное сокращение для того, чтобы получить либо значение переменной, либо значение
//по умолчанию 
var suspendTime = app.sessionState.suspendTime || 0;

//Определяет, сколько времени, в секундах, прошло
var elapsed = ((new Date().getTime()) - suspendTime) / 1000;

//Обновляет ленту, если > 1 часа (или, для тестирования, используйте меньшее значение)
if (elapsed > 3600) {	
downloadPosts();	
}	
});	

Для того, чтобы протестировать этот код, загрузите отладчик Visual Studio и установите точки останова в этих событиях. Затем щёлкните на пункт приостановки (suspend) на панели инструментов (можете вернуться к Рис. 3.7), и вы должны попасть в обработчик события checkpoint. Подождите несколько секунд и щелкните на кнопку возобновления работы приложения (resume), имеющую треугольный зеленый значок, и вы должны оказаться в обработчике события resuming. Затем вы можете пошагово исполнить код и увидеть, что переменная elapsed содержит количество прошедших секунд, и если вы измените её значение (или замените 3600 меньшим значением), вы можете увидеть, как downloadPosts снова вызывается для обновления данных.

А что насчёт запуска после остановки работы приложения? Хорошо, если вы не кэшировали данные ранее, вам, в любом случае, нужно их обновить. Если вы кэшировали какие-то данные, сохранённое состояние сеанса работы приложения (такое, как отметка времени) поможет вам решить, стоит ли использовать кэшированные данные или нужно загрузить их снова.

Полезно будет упомянуть здесь о том, что вы можете использовать механизмы HTML5, наподобие localStorage, IndexedDB и кэша приложения для целей кэширования; подобные данные хранятся в папке локальных данных приложения. И, если говорить о базах данных, вы можете заинтересоваться, что доступно приложениям для Магазина Windows помимо IndexedDB. Одна из возможностей - это SQLite, как описано в материале "Использование SQLite в приложениях для Магазина Windows" (http://timheuer.com/blog/archive/2012/05/20/using-sqlite-in-metro-style-app.aspx) (в блоге Тима Хейера, одного из инженеров Windows 8). Кроме того, вы можете использовать библиотеку OData для JavaScript, которую можно найти по адресу http://www.odata.org/libraries. Это - один из наиболее лёгких способов для взаимодействия с онлайновыми базами данных на SQL Server (или любыми другими, поддерживающими OData), так как он просто использует возможности XmlHttpRequest.

Работа с сетевыми подключениями (вкратце)

Мы поговорим о сетевом взаимодействии в "Анатомия приложения и навигация по страницам" курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", но есть один важный аспект, о котором вы, разрабатывая приложение, должны знать заранее. Что делает приложение с изменениями состояния сетевого соединения, такими как разъединение, повторное соединение и с изменениями полосы пропускания и стоимости соединения (такими, как связь в роуминге, в другой зоне предоставления услуг)?

API Windows.Networking.Connectivity обеспечивает вас такого рода информацией. Вот три основных способа реагирования на подобные события:

  • Во-первых, имейте достаточное количество оффлайновых данных для случаев, когда соединение теряется. Кэшируйте важные данные, ставьте в очередь задачи, которые будут выполнены позднее и продолжайте предоставлять пользователю весь функционал, который можете предоставить без подключения к сети. Очевидно, это близко связано с общей стратегией управления состояниями приложения. Например, если соединение с сетью потеряно, когда приложение приостановлено, вы не сможете обновить все ваши данные, поэтому приготовьтесь к подобному! С другой стороны, если приложение оказалось отключённым от сети, когда оно приостановлено, при возобновлении работы проверьте возможность подключения.
  • Во-вторых, следите за изменениями состояния сети для того, чтобы узнать, когда подключение восстановлено, и затем выполните задания, поставленные в очередь, перекэшируйте данные и так далее.
  • В-третьих, следите за изменениями сетевого соединения в сетях с лимитными тарифными планами для того, чтобы обеспечить сохранность денежных средств пользователя. Раздел 4.5. "Сертификационных требований к приложениям для Windows 8" (http://msdn.microsoft.com/library/windows/apps/hh694083.aspx), на самом деле, касается защиты пользователя от "шокирующего счёта", причиной которого стали чрезмерные объемы передачи данных в подобных сетях. Будьте уверены, последнее, что вам хотелось бы видеть среди отзывов в Магазине Windows - это отрицательные отзывы, вызванные подобными проблемами.

Проще говоря, тестируйте приложение как в состоянии подключения к сети, так и без него для того, чтобы обнаружить небольшие оплошности в коде. В "Here My Am!", например, первая версия скрипта в html/map.html не заботилась о том, чтобы проверить, действительно ли загружен удалённый скрипт карт Bing. Сейчас она проверяет, является ли допустимым пространство имен Microsoft (для Microsoft.Maps.Map) В SimpleXhr, тоже, я добавил обработчик ошибки для операции получения отложенного результата в WinJS.xhr, в итоге я могу, по меньшей мере, отобразить обычное сообщение. Здесь, конечно, гораздо больше работы, но попытайтесь, по меньшей мере, охватить основы, для того, чтобы избежать исключений, которые приведут к аварийному завершению работы приложения.

Секреты и советы для WinJS.xhr

Не распутывая клубок проблем, которым является XmlHttpRequest, будет полезным посмотреть на пару дополнительных вещей, касающихся WinJS.xhr.

Во-первых, обратите внимание на то, что одиночный аргумент этой функции является объектом, который содержит множество свойств. Свойство url наиболее распространено, конечно, но вы, кроме того, можете установить свойство type (тип) (по умолчанию оно установлено в "GET") и respondeType для транзакций другого рода, информацию об учетных данных пользователя в виде user и password, установить headers (заголовки) (такие, как "If-Modified-Since" с датой для управления кэшированием) и предоставить любые другие дополнительные данные (data), если в этом есть необходимость (такие, как параметры запроса к базе данных для XHR). Кроме того, вы можете поддерживать функцию customRequestInitializer, которая будет вызываться объектом XmlHttpReauest до отправки данных, позволяя вам произвести любые необходимые действия.

Во-вторых, это установка тайм-аута в запросе. Вы можете использовать для этих целей customRequestInitializer, устанавливая свойство XmlHttpRequest.timeout и, возможно, обрабатывая событие ontimeout. С другой стороны, как мы увидим в разделе "Завершение истории promise-объектов" в конце этой лекции, вы можете использовать функцию WinJS.Promise.timeout, которая позволяет вам устанавливать период тайм-аута, после которого promise-вызов (и асинхронная операция, связанная с ним) будет отменен. Отмена производится простым вызовом метода cancel promise-объекта.

Вам может понадобиться включить WinJS.xhr в другой promise-объект, это мы так же рассмотрим в конце данной лекции. Вы можете сделать это для того, чтобы инкапсулировать другие промежуточные результаты с вызовом XHR, когда ваш код просто использует возвращённый отложенных результат обычным образом. В соединении с тайм-аутами, эта так же может быть использовано для реализации механизма множественного повтора.

Далее, если вам нужно скоординировать вместе несколько вызовов XHR, вы можете использовать WinJS.Promise.join, который мы снова увидим позже.

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

У приложений для Магазина Windows, использующих XHR с localhost: URI (локальная заглушка, петлевой адрес локальной сети) заблокированы при проектировании системы. При разработке, однако, это очень полезно для отладки сервисов без их развёртывания. Вы можете включить локальную заглушку в Visual Studio, открыв диалоговое окно свойств проекта (контекстное меню проекта > Свойства), выбрав группу Отладка (Debugging) в левой части окна и установив параметр Разрешить петлевой адрес в локальной сети (Allow Local Network Loopback) в значение Да. Мы рассмотрим соответствующий пример в "Быстрый старт" курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", где это окажется очень полезным для отладки сервисов, которые выдают данные для обновления плиток и другие оповещения.

Наконец, полезно знать, что по соображениям безопасности куки-данные (cookies) автоматически вырезаются из XHR-данных, поступающих в локальный контекст. Один из способов это обойти - выполнять XHR-вызовы из iframe, находящегося в веб-контексте (в котором вы можете использовать WinJS.XHR), затем извлекать куки-данные, которые вам нужны и передавать их в локальный контекст, используя postMessage. С другой стороны, вы можете решить проблему на стороне сервиса, такую, как реализацию API, предоставляющего необходимые вам данные, которые вы извлекали из куки, напрямую.

Другие подробности о данной функции вы можете найти в документации по WinJS.xhr (http://msdn.microsoft.com/library/windows/apps/br229787.aspx), и по ссылкам на дополнительные материалы, которые она содержит.