Главная » Разработка для Windows Phone 7 » Датчики и службы Windows Phone 7

0

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

Ни акселерометр, ни служба определения местоположения не будут корректно работать в открытом космосе. Это их роднит.

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

Акселерометр

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

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

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

Выходные данные акселерометра удобно представить в виде трехмерного вектора. Обычно векторы записываются полужирным шрифтом, поэтому вектор ускорения может быть обозначен так: (x, y, z). В XNA есть тип трехмерный вектор, в Silverlight нет.

Тогда как три координаты (x, y, z) точно определяют точку в пространстве, вектор (x, y, z) обозначает направление и величину. Очевидно, что точка и вектор взаимосвязаны. Направление вектора (x, y, z) – это луч из точки (0, 0, 0) в точку (x, y, z). Но вектор (x, y, z) это совсем не отрезок, соединяющий (0, 0, 0) и (x, y, z). Это направление этого отрезка.

Для вычисления модуля вектора (x, y, z) используется трехмерная форма теоремы Пифагора:

Модуль = 

Для работы с акселерометром ассоциируем с телефоном трехмерную систему координат. Не важно, как ориентирован телефон, но положительное направление оси Y будет проходить вдоль максимального размера телефона снизу (где располагаются кнопки) вверх, и положительное направление оси X – слева направо. Ось Z направлена перпендикулярно телефону, прямо на пользователя.

Это традиционная трехмерная система координат, такая же используется при создании 3D- графики на XNA. Ее называют правой системой координат. Расположите указательный палец правой руки вдоль положительного направления Х, средний палец – вдоль положительного направления Y, и большой палец будет указывать на положительное направление Z.

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

Когда телефон неподвижен, вектор акселерометра указывает на землю. Модуль равен 1, т.е. 1g1, что соответствует силе притяжения на поверхности земли. Когда телефон расположен вертикально, вектор ускорения – (0, -1, 0), т.е. направлен прямо вниз.

Поверните телефон на 90° против часовой стрелки (так называемое левостороннее альбомное расположение), и вектор ускорения станет равным (-1, 0, 0); переверните телефон «вверх ногами» – вектор равен (0, 1, 0); еще один поворот на 90° против часовой стрелки обеспечивает правостороннее альбомное расположение и вектор ускорения равный (1, 0, 0). Положите телефон на стол экраном вверх, и вектор ускорения будет (0, 0, -1). (Эмулятор Windows Phone 7 всегда возвращает это значение.)

Конечно, вектор ускорения редко будет принимать такие точные значения, даже его модуль не будет точно равен 1. Для неподвижного телефона значения модуля могут колебаться в пределах нескольких процентов для разных ориентаций. На Луне величина вектора ускорения Windows Phone 7 будет в районе 0,17, но мобильная связь неустойчивая.

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

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

При свободном падении модуль вектора ускорения, теоретически, должен стремиться к нулю.

Для работы с акселерометром используется библиотека Microsoft.Devices.Sensors и пространство имен Microsoft.Devices.Sensors. В файле WMAppManifest.xml необходимо указать:

<Capability Name="ID_CAP_SENSORS" />

Это задается по умолчанию.

В приложении создается экземпляр класса Accelerometer (Акселерометр), задается обработчик события ReadingChanging (Изменение показаний прибора) и вызывается метод Start.

И тут начинаются фокусы. Рассмотрим проект SilverlightAccelerometer.project, который просто обеспечивает выведение на экран текущих показаний. Центрированный TextBlock описывается в файле XAML:

Проект Silverlight: SilverlightAccelerometer Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock Name="txtblk"

HorizontalAlignment="Center" VerticalAlignment="Center" />

</Grid>

Это приложение будет обеспечивать вывод на экран вектора акселерометра на протяжении всего времени его выполнения, поэтому создание класса Accelerometer и вызов метода Start происходит в конструкторе:

Проект Silverlight: SilverlightAccelerometer Файл: MainPage.xaml.cs (фрагмент)

public MainPage() {

InitializeComponent();

Accelerometer acc = new Accelerometer(); acc.ReadingChanged += OnAccelerometerReadingChanged;

try {

acc.Start();

}

catch (Exception exc) {

txtblk.Text = exc.Message;

}

}

Документация предупреждает, что вызов Start может привести к формированию исключения, поэтому приложение защищает себя от этого. Класс Accelerometer также поддерживает методы Stop (Остановить) и Dispose (Удалить), но в данной программе они не используются. Для обеспечения сведения о доступности акселерометра и его состоянии предоставляется свойство State.

Событие ReadingChanged обеспечивается классом аргументов события AccelerometerReadingEventArgs. Этот объект имеет свойства X, Y и Zтипа double и свойство TimeStamp типа DateTimeOffset (Смещение даты-времени). В приложении SilverlightAccelerometer задача обработчика события – форматировать эти данные в строку и задать ее как значение свойства Text объекта TextBlock.

Загвоздка здесь в том, что обработчик события (в данном случае,

OnAccelerometerReadingChanged (При изменении показаний акселерометра)) вызывается в другом потоке выполнения. Это означает, что он должен обрабатываться особым образом.

Немного теории. Создание и доступ ко всем элементам и объектам пользовательского интерфейса в приложении на Silverlight выполняется в основном потоке выполнения, который часто называют потоком пользовательского интерфейса или UI-потоком. Эти объекты пользовательского интерфейса не являются потокобезопасными; для них не предусмотрен одновременный доступ из множества потоков. Поэтому Silverlight не допустит доступа к объекту пользовательского интерфейса из потока, не являющегося UI-потоком.

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

К счастью в пространстве имен System.Windows.Threading описан класс Dispatcher (Диспетчер), который обеспечивает решение этой проблемы. Через класс Dispatcher можно направлять задания из потока, не являющегося UI-потоком, в очередь, которая позднее будет обработана UI-потоком. Все это звучит довольно запутанно, но с точки зрения разработчика все довольно просто, потому что эти задания принимают форму простых вызовов методов.

Экземпляр Dispatcher уже доступен. Класс DependencyObject (Зависимость) описывает свойство Dispatcher типа Dispatcher, и от DependencyObject наследуется множество классов Silverlight. С экземплярами всех этих классов можно работать из потоков, не являющихся UI- потоками, потому что все они имеют свойства Dispatcher. Из любого производного от DependencyObject класса, созданного в UI-потоке, может использоваться любой объект Dispatcher. Они все одинаковые.

Класс Dispatcher определяет метод CheckAccess (Проверить возможность доступа), который возвращает true, если текущий поток имеет доступ к конкретному объекту пользовательского интерфейса. (Метод CheckAccess также дублируется самим DependencyObject.) Для случаев, когда доступ к объекту из текущего потока невозможен, Dispatcher предоставляет две версии метода Invoke (Вызвать), с помощью которых выполняется отправка заданий для UI-потока.

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

В развернутой версии требуется делегат и метод, соответствующий этому делегату. Делегат (и метод) не должен иметь возвращаемого значения, но у него должно быть такое количество аргументов, которого будет достаточно для выполнения задания. В данном случае это задание состоит в присвоении строки в качестве значения свойства Text объекта TextBlock:

Проект: SilverlightAccelerometer Файл: MainPage.xaml.cs (фрагмент)

delegate void SetTextBlockTextDelegate(TextBlock txtblk, string text);

void SetTextBlockText(TextBlock txtblk, string text) {

txtblk.Text = text;

OnAccelerometerReadingChanged отвечает за вызов SetTextBlockText (Задать текст блока текста). Он впервые использует CheckAccess, чтобы определить возможность вызова метода SetTextBlockText напрямую. Если это невозможно, обработчик вызывает метод BeginInvoke (Начать вызов). Его первым аргументом является экземпляр делегата с методом SetTextBlockText. За ним следуют все необходимые SetTextBlockText аргументы:

Проект: SilverlightAccelerometer Файл: MainPage.xaml.cs (фрагмент)

void OnAccelerometerReadingChanged(object sender, AccelerometerReadingEventArgs

args) {

string str = String.Format("X = {0:F2}\n" +

"Y = {1:F2}\n" + "Z = {2:F2}\n\n" + "Magnitude = {3:F2}\n\n" + "{4}",

args.X, args.Y, args.Z,

Math.Sqrt(args.X * args.X + args.Y * args.Y +

args.Z * args.Z),

args.Timestamp);

if (txtblk.CheckAccess()) {

SetTextBlockText(txtblk, str);

}

else {

txtblk.Dispatcher.BeginInvoke(new SetTextBlockTextDelegate(SetTextBlockText),

txtblk, str);

}

}

Это не самый плохой вариант, но необходимость перепрыгивать из потока в поток повлекла за собой применение дополнительного метода и делегата. А можно ли все сделать прямо в обработчике событий?

Да! У метода BeginInvoke есть перегруженный вариант, принимающий делегат Action, который определяет метод, не имеющий возвращаемого значения и аргументов. Можно создать анонимный метод прямо в вызове BeginInvoke. Полный код, следующий за созданием строкового объекта, выглядит следующим образом:

if (txtblk.CheckAccess()) {

txtblk.Text = str;

}

else {

txtblk.Dispatcher.BeginInvoke(delegate() {

txtblk.Text = str;

});

}

Анонимный метод начинается с ключевого слова delegate (делегат), за которым следует тело метода, заключенное в фигурные скобки. Пустые круглые скобки после ключевого слова delegate не являются обязательными.

Анонимный метод также может быть описан с помощью лямбда-выражения:

if (txtblk.CheckAccess()) {

txtblk.Text = str;

else {

txtblk.Dispatcher.BeginInvoke(() => {

txtblk.Text = str;

});

}

Дублирующийся код, задающий str свойству Text объекта TextBlock, выглядит некрасиво (и нежелателен, если бы выражений было больше одного), но вызов CheckAccess, на самом деле, не нужен. Можно просто вызвать BeginInvoke, и ничего плохого не случится, если он вызывается из UI-потока.

В эмуляторе Windows Phone 7 нет акселерометра, поэтому он всегда возвращает значение (0, 0, -1), свидетельствующее о том, что телефон лежит на плоской поверхности. Выполнять это приложение имеет смысл только на реальном телефоне:

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

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии