В этой статье мы рассмотрим, как вы можете использовать аппаратные возможности устройства Windows Phone, такие как геолокация, датчики устройства, Bluetooth и NFC.
геолокации
Все устройства Windows Phone имеют встроенное оборудование для геолокации. Используя комбинацию сигналов 3G, Wi-Fi и GPS, телефон может определить местоположение пользователя и сделать его доступным для каждого приложения благодаря набору API, включенному в среду выполнения Windows.
Геолокация — это еще один сценарий, в котором API дублируются. Исходный набор API был частью платформы Silverlight, но он был расширен в среде выполнения Windows.
Новый основной класс для работы со службами геолокации называется Geolocator
и является частью пространства имен Windows.Devices.Geolocation
.
Примечание. Чтобы использовать службы геолокации, вам нужно включить возможность ID_CAP_LOCATION
в файле манифеста.
Первым шагом для использования сервисов геолокации является проверка значения свойства LocationStatus
класса Geolocator
для определения текущего статуса сервисов. В частности, мы должны управлять состоянием PositionStatus.Disabled
. В этом случае пользователь отключил службы геолокации в настройках телефона, поэтому нам не нужно выполнять никаких операций, связанных с геолокацией, в противном случае мы получим исключение.
Если вы хотите отслеживать состояние службы геолокации, есть специальный обработчик событий, называемый StatusChanged
который вызывается каждый раз, когда изменяется состояние. Это помогает вам определить, например, что GPS готов или что пользователь находится в месте, которое трудно отследить.
Существует два способа взаимодействия с классом Geolocator
: запрос одной позиции (например, клиента Twitter, который должен геолокализовать твит) или подписка на обработчик событий, который можно использовать для постоянного отслеживания местоположения пользователя (например, , приложение бегового трекера).
Чтобы запросить одну позицию, вам просто нужно вызвать асинхронный метод GetGeopositionAsync()
. Он вернет объект Geoposition
содержащий свойство Coordinate
, которое поможет вам определить, где находится пользователь.
Примечание. Объект CivicAddress
имеет другое свойство, называемое CivicAddress
которое должно содержать ссылку на местоположение пользователя с использованием гражданских ссылок (таких как город, адрес улицы и т. Д.). Это свойство не поддерживается, поэтому оно всегда возвращает неверную информацию. Далее в этой статье мы рассмотрим, как получить гражданскую позицию пользователя.
Следующий пример кода демонстрирует, как получить позицию одного пользователя:
1
2
3
4
5
6
7
8
9
|
private async void OnGetSinglePositionClicked(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
if (geolocator.LocationStatus != PositionStatus.Disabled)
{
Geoposition geoposition = await geolocator.GetGeopositionAsync();
MessageBox.Show(string.Format(«The user’s coordinates are {0} — {1}», geoposition.Coordinate.Latitude, geoposition.Coordinate.Latitude))
}
}
|
Вместо этого, чтобы непрерывно отслеживать позицию пользователя, вам необходимо подписаться на обработчик событий PositionChanged
, который вызывается каждый раз, когда пользователь перемещается из предыдущего местоположения. Вы можете контролировать частоту возникновения этого события, задав три свойства объекта Geolocator
:
-
DesiredAccuracy
, которая является точностью геолокации. Чем выше оно установлено, тем более точным будет результат и будет потребляться больше энергии аккумулятора. -
MovementThreshold
, который представляет собой расстояние в метрах, которое пользователь должен переместить из предыдущего местоположения, прежде чем будет инициировано событиеPositionChanged
. -
ReportInterval
— минимальное количество миллисекунд, которое должно пройти между двумя обнаружениями.
Событие PositionChanged
возвращает параметр, который содержит свойство Position
типа Geoposition
— оно работает так же, как мы ранее видели для GetGeopositionAsync()
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
private void OnStartTrackingPosition(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
geolocator.MovementThreshold = 100;
geolocator.ReportInterval = 1000;
geolocator.DesiredAccuracy = PositionAccuracy.High;
geolocator.PositionChanged += geolocator_PositionChanged;
}
private void geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs args)
{
Dispatcher.BeginInvoke(() =>
{
Latitude.Text = args.Position.Coordinate.Latitude.ToString();
Longitude.Text = args.Position.Coordinate.Longitude.ToString();
});
}
|
В предыдущем примере мы отслеживаем местоположение пользователя каждый раз, когда он или она перемещается на 100 метров от предыдущего местоположения, только если с момента предыдущего обнаружения прошла хотя бы одна секунда. Каждый раз, когда возникает событие PositionChanged
, мы отображаем значения свойств Latitude
и Longitude
в двух разных элементах управления TextBlock
. Обратите внимание, что мы используем класс Dispatcher
мы обсуждали ранее в этой серии. Это необходимо, потому что событие PositionChanged
управляется в фоновом потоке, поэтому мы не можем напрямую взаимодействовать с пользовательским интерфейсом.
Совет: Вы можете легко протестировать сервисы геолокации, используя инструмент, который поставляется вместе с эмулятором. Просто щелкните позицию на карте, и соответствующие координаты будут отправлены в эмулятор.
Отслеживание фона
В Windows Phone 8 появилась возможность отслеживать положение пользователя, когда приложение приостановлено. Это означает, что обработчик события PositionChanged
будет по-прежнему вызываться, даже если приложение не работает на переднем плане.
Фоновое отслеживание может продолжаться, если:
- приложение прекращает отслеживать местоположение пользователя,
StatusChanged
обработчиков событийPositionChanged
иStatusChanged
- приложение работает в фоновом режиме в течение четырех часов без повторного открытия
- Режим энергосбережения включен
- на телефоне не хватает свободной памяти
- геолокационные сервисы отключены
- пользователь открывает другое приложение, которое может отслеживать его или ее местоположение в фоновом режиме
Чтобы активировать фоновое отслеживание, необходимо отредактировать файл манифеста с помощью ручного редактора (щелкните файл правой кнопкой мыши и выберите « Просмотреть код» ), поскольку этот параметр не поддерживается визуальным редактором. Вам нужно будет отредактировать узел DefaultTask
следующим образом:
1
2
3
4
5
6
7
|
<Tasks>
<DefaultTask Name=»_default» NavigationPage=»MainPage.xaml»>
<BackgroundExecution>
<ExecutionType Name=»LocationTracking»/>
</BackgroundExecution>
</DefaultTask>
</Tasks>
|
Теперь, если предыдущие условия выполнены, приложение будет отслеживать местоположение пользователя, даже если оно не работает на переднем плане.
Если вы хотите настроить взаимодействие с пользователем в зависимости от того, работает ли приложение в фоновом режиме, вы можете подписаться на конкретное событие объекта PhoneApplicationService
, которое объявлено в файле App.xaml
и которое мы научились использовать ранее в этом серия . Обработчик событий называется RunningInBackground
и он срабатывает каждый раз, когда приложение приостанавливается. Однако, поскольку он использует службы геолокации, он будет работать в фоновом режиме.
В следующем примере вы можете увидеть, как будет выглядеть объявление PhoneApplicationService
после того, как мы подписались на событие:
1
2
3
4
5
6
7
8
|
<Application.ApplicationLifetimeObjects>
<!—Required object that handles lifetime events for the application—>
<shell:PhoneApplicationService
Launching=»Application_Launching» Closing=»Application_Closing»
Activated=»Application_Activated» Deactivated=»Application_Deactivated»
RunningInBackground=»Application_RunningInBackground»
/>
</Application.ApplicationLifetimeObjects>
|
В предыдущем примере мы установили свойство (называемое IsRunningInBackground
), чтобы узнать, работает ли приложение в фоновом режиме. Мы устанавливаем его в true, когда RunningInBackground
событие RunningInBackground
, и устанавливаем в false, когда возникает событие Activated
, что означает, что приложение было вновь открыто.
Взаимодействие с элементом управления картой
Windows Phone включает встроенный элемент управления Map
который можно использовать для встраивания карты в страницу приложения и в сочетании со службами геолокации. Со времени Windows Phone 7 управление было значительно улучшено, и теперь оно основано на картографии Nokia. Кроме того, он поддерживает автономные карты — если пользователь загрузил карты для текущего местоположения, элемент управления сможет автоматически использовать их. Элемент управления Map
является частью пространства имен Microsoft.Phone.Maps.Controls
, поэтому вам необходимо добавить его на свою страницу XAML перед использованием:
1
|
xmlns:maps=»clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps»
|
Элемент управления Map
предоставляет множество свойств для настройки. Самые полезные из них:
-
Center
, координаты геолокации, в центре которой находится карта. -
ZoomLevel
— уровень масштабирования от1
(минимум) до19
(максимум). -
CartographicMode
, который можно использовать для переключения междуAerial
(вид со спутника),Road
(вид с дороги),Terrain
(вид ландшафта) иHybrid
(комбинация других). -
ColorMode
может быть использован для установкиDark
илиLight
тем в зависимости от условий освещенности.
В следующем примере вы можете увидеть элемент управления Map
включенный в страницу:
1
2
3
4
|
<maps:Map x:Name=»CustomMap»
ZoomLevel=»15″
CartographicMode=»Aerial»
ColorMode=»Light» />
|
Использовать элемент управления Map
в сочетании со службами геолокации должно быть просто: достаточно установить свойство Center
объекта Map с помощью объекта Geoposition
возвращаемого классом Geolocator
. К сожалению, есть ограничение, поскольку Windows Phone и элемент управления Map
используют два разных класса для хранения координат геолокации. Первый называется Geocoordinate
и он является частью пространства имен Windows.Devices.Geolocation
, а второй называется GeoCoordinate
(с большой буквы) и является частью System.Device.Location
имен System.Device.Location
.
К счастью, есть обходной путь: установка инструментария Windows Phone, который мы обсуждали ранее в этой серии . Помимо предоставления полезного набора дополнительных элементов управления, он также предлагает множество помощников и расширений, которые полезны при работе с элементом управления Map
.
В частности, после добавления Windows Phone Toolkit в ваш проект (самый простой способ — использование NuGet ), вы сможете использовать метод расширения ToGeoCoordinate()
, который может преобразовать исходный класс среды выполнения Windows в определенный элемент управления Map
. , В следующем примере вы можете увидеть, как мы используем его для отображения текущего местоположения пользователя на карте:
1
2
3
4
5
6
7
8
9
|
private async void OnGetSinglePositionClicked(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
if (geolocator.LocationStatus != PositionStatus.Disabled)
{
Geoposition geoposition = await geolocator.GetGeopositionAsync();
myMap.Center = geoposition.Coordinate.ToGeoCoordinate();
}
}
|
Слои
Элемент управления « Map
поддерживает слои, которые можно добавить поверх карты. Слой — это коллекция визуальных объектов, которые отображаются на карте, и он представлен классом MapLayer
.
Каждый MapLayer
имеет коллекцию наложений (класс MapOverlay
); каждый из них является объектом, который отображается на карте.
Наложение может состоять практически из любого элемента управления. Фактически, это определяется позицией, использующей свойство GeoCoordinate
, и контентом, использующим свойство Content
, которое является универсальным объектом. Это означает, что вы можете добавить любой объект, который вы хотите, как контент, как с любым из визуальных элементов управления XAML, доступных в платформе.
В следующем примере мы создаем объект Rectangle
и устанавливаем его в качестве MapOverlay
объекта MapOverlay
который мы добавляем в новый слой.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
private void OnAddShapeClicked(object sender, RoutedEventArgs e)
{
MapOverlay overlay = new MapOverlay
{
GeoCoordinate = myMap.Center,
Content = new Rectangle
{
Fill = new SolidColorBrush(Colors.Blue),
Width = 40,
Height = 40
}
};
MapLayer layer = new MapLayer();
layer.Add(overlay);
myMap.Layers.Add(layer);
}
|
Маршрутизация
Распространенным сценарием, в котором вы работаете с Map
управления « Map
является маршрутизация, при которой вы можете отображать маршруты на карте. Даже если это звучит сложно, это легко реализовать с RouteQuery
класса RouteQuery
, который позволяет:
- установить различные параметры маршрута с помощью свойств
TravelMode
иRouteOptimization
- добавить список путевых точек, составляющих маршрут
Следующий код демонстрирует пример маршрутизации:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
private void OnCalculateRouteClicked(object sender, RoutedEventArgs e)
{
RouteQuery query = new RouteQuery
{
TravelMode = TravelMode.Driving,
RouteOptimization = RouteOptimization.MinimizeTime,
};
List<GeoCoordinate> coordinates = new List<GeoCoordinate>();
coordinates.Add(new GeoCoordinate(47.6045697927475, -122.329885661602));
coordinates.Add(new GeoCoordinate(47.605712890625, -122.330268859863));
query.Waypoints = coordinates;
query.QueryCompleted += query_QueryCompleted;
query.QueryAsync();
}
|
Параметры TravelMode
и RouteOptimization
можно использовать для настройки маршрута так же, как это делают многие GPS-навигаторы. В предыдущем примере нам нужен маршрут вождения, который требует наименьшего количества времени.
Путь задается с помощью свойства Waypoints
, для которого требуется набор объектов GeoCoordinate
. Каждый объект представляет точку на пути, которая должна быть затронута маршрутом.
Класс RouteQuery
работает с использованием метода обратного вызова. Мы вызываем метод QueryAsync()
и подписываемся на событие QueryCompleted
, которое запускается при расчете маршрута, как показано в следующем примере:
1
2
3
4
5
|
void query_QueryCompleted(object sender, QueryCompletedEventArgs<Route> e)
{
MapRoute route = new MapRoute(e.Result);
myMap.AddRoute(route);
}
|
Отобразить маршрут в элементе управления Map
очень просто: вам просто нужно создать новый объект MapRoute
, передав результат запроса (сохраненный в свойстве Result
возвращаемого объекта) в качестве параметра, и добавить его на Map
с помощью AddRoute()
метод.
Работа с координатами
До сих пор мы всегда работали с координатами геолокации, основанными на широте и долготе, но часто пользователям проще понять местоположение на основе его гражданского адреса. В Windows Phone 8 введены два класса для выполнения геокодирования: GeoCodeQuery
(для преобразования адреса в набор числовых координат) и ReverseGeocodeQuery
(для преобразования широты и долготы в адрес).
Они оба работают одинаково, поскольку используют тот же подход обратного вызова, который мы видели для маршрутизации. После того, как вы определили операцию, которую нужно выполнить, вы можете начать поиск с помощью QueryAsync()
. После завершения поиска вы можете использовать QueryCompleted
событий QueryCompleted
для управления результатами.
Класс GeocodeQuery
требует, чтобы вы установили два параметра: GeoCoordinate
и, самое главное, SearchTerm
, который является ключевым словом поиска. GeoCoordinate
не требуется (поскольку целью этого класса является поиск координаты местоположения), но вам все равно придется установить его с поддельным значением, как показано в следующем примере. В противном случае вы не получите никакого результата.
01
02
03
04
05
06
07
08
09
10
|
private void OnResolveCoordinatesClicked(object sender, RoutedEventArgs e)
{
GeocodeQuery query = new GeocodeQuery
{
GeoCoordinate = new GeoCoordinate(0, 0),
SearchTerm = «Milan,Italy»
};
query.QueryCompleted += query_QueryCompleted;
query.QueryAsync();
}
|
ReverseGeoCodeQuery
класс ReverseGeoCodeQuery
требует, чтобы свойство GeoCoordinate
устанавливалось только с координатами местоположения.
1
2
3
4
5
6
7
8
9
|
private void OnResolveAddressClicked(object sender, RoutedEventArgs e)
{
ReverseGeocodeQuery reverseQuery = new ReverseGeocodeQuery
{
GeoCoordinate = new GeoCoordinate(45.3967, 9.3163)
};
reverseQuery.QueryCompleted += reverseQuery_QueryCompleted;
reverseQuery.QueryAsync();
}
|
QueryCompleted
события QueryCompleted
одинаков для обоих классов и возвращает коллекцию объектов MapLocation
. Если вы использовали класс GeocodeQuery
, вас, вероятно, заинтересует объект GeoCoordinate
, который содержит широту и долготу искомого местоположения.
1
2
3
4
5
|
void query_QueryCompleted(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
var item = e.Result.FirstOrDefault();
myMap.SetView(item.BoundingBox, MapAnimationKind.Parabolic);
}
|
В предыдущем примере показан способ центрирования карты на позиции, возвращаемой службой.
Вместо этого, если вы использовали класс ReverseGeocodeQuery
, вы найдете нужную Information
свойстве Information
которое содержит такие данные, как City
, Street
, Address
и т. Д.
1
2
3
4
5
|
void reverseQuery_QueryCompleted(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
var item = e.Result.FirstOrDefault();
MessageBox.Show(string.Format(«{0},{1}», item.Information.Address.Street, item.Information.Address.City));
}
|
В обоих случаях обработчик события возвращает коллекцию объектов MapLocation
потому что, особенно если вы искали местоположение по ключевому слову, служба может вернуть несколько результатов. Предыдущий пример показывает информацию о первом элементе в коллекции.
Как опубликовать приложение, использующее элемент управления картой
Вы можете свободно использовать элемент управления Map
на этапе тестирования, но когда вы отправляете его в Магазин, вам понадобятся производственные учетные данные.
Полномочия могут быть получены во время процесса представления. Один из необязательных шагов называется Map service
, который предоставит вам два кода с именами ApplicationId
и AuthenticationToken
. Когда у вас есть коды, вам нужно установить их следующим образом при запуске приложения:
1
2
|
Microsoft.Phone.Maps.MapsSettings.ApplicationContext.ApplicationId = «ApplicationId»;
Microsoft.Phone.Maps.MapsSettings.ApplicationContext.AuthenticationToken = «AuthenticationToken»;
|
Датчики движения
Устройства Windows Phone имеют много датчиков движения, которые могут использоваться такими приложениями, как акселерометр, гироскоп и компас. Среда выполнения Windows представила новый набор API, которые являются частью пространства имен Windows.Devices.Sensors
:
- Класс
Accelerometer
можно использовать для взаимодействия с акселерометром. - Класс
Gyrometer
может использоваться для взаимодействия с гироскопом. - Класс
Compass
может использоваться для взаимодействия с компасом. -
OrientationSensor
— это специальный класс, который может объединять значения, полученные от всех доступных датчиков.
Примечание. Чтобы использовать датчики, вам нужно включить опцию ID_CAP_SENSORS
в файле манифеста. Если вы также хотите использовать гироскоп и компас, вам необходимо включить возможности ID_REQ_MAGNETOMETER
и ID_REQ_GYROSCOPE
в разделе Требования файла манифеста. Таким образом, пользователи с устройствами без одного из этих датчиков не смогут загрузить ваше приложение.
Все датчики работают одинаково. Вы сможете получить ссылку на датчик, используя метод GetDefault()
. Если он не доступен по телефону (например, не все устройства имеют гироскоп), вы получите взамен null
ссылку. Важно всегда проверять, является ли возвращаемый датчик null
прежде чем выполнять какие-либо операции.
Как и в случае служб геолокации, у вас есть два способа взаимодействия с датчиками:
- метод
GetCurrentReading()
, который возвращает одно обнаружение -
ReadingChanged
событийReadingChanged
, который срабатывает каждый раз, когда телефон перемещается на новую позицию
В этом разделе мы будем использовать в качестве примера класс OrientationSensor
, который является специальным датчиком, который может объединять все значения, возвращаемые доступными датчиками, и автоматически фильтровать все данные вне масштаба. Класс возвращает объект OrientationSensorReading
, который содержит всю информацию о текущей позиции. Вы сможете определить положение устройства, используя свойства Quaternion
и RotationMatrix
.
В следующих примерах вы можете увидеть два способа достижения одного и того же результата: получить одно чтение и подписаться на уведомления, которые отправляются при каждом изменении позиции. Координаты устройства по осям X, Y и Z отображаются на экране с помощью трех элементов управления TextBlock
:
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
39
|
//Single reading.
private void OnGetReadingClicked(object sender, RoutedEventArgs e)
{
OrientationSensor orientationSensor = OrientationSensor.GetDefault();
if (orientationSensor != null)
{
OrientationSensorReading reading = orientationSensor.GetCurrentReading();
txtX.Text = reading.Quaternion.X.ToString();
txtY.Text = reading.Quaternion.Y.ToString();
txtZ.Text = reading.Quaternion.Z.ToString();
}
else
{
MessageBox.Show(«The sensor is not available»);
}
}
//Continuous reading.
private void OnGetReadingClicked(object sender, RoutedEventArgs e)
{
OrientationSensor orientationSensor = OrientationSensor.GetDefault();
if (orientationSensor != null)
{
orientationSensor.ReadingChanged += orientationSensor_ReadingChanged;
}
else
{
MessageBox.Show(«The sensor is not available»);
}
}
void orientationSensor_ReadingChanged(OrientationSensor sender, OrientationSensorReadingChangedEventArgs args)
{
Dispatcher.BeginInvoke(() =>
{
txtX.Text = args.Reading.Quaternion.X.ToString();
txtY.Text = args.Reading.Quaternion.Y.ToString();
txtZ.Text = args.Reading.Quaternion.Z.ToString();
});
}
|
Обратите внимание, что если вы решите подписаться на событие ReadingChanged
, вам потребуется Dispatcher
для связи с пользовательским интерфейсом, поскольку обработчик события управляется фоновым потоком.
Если вам нужно использовать определенный датчик, код для использования очень похож. Вам просто нужно использовать определенный класс датчиков и управлять конкретным объектом чтения, который вы получите взамен.
Совет: эмулятор Windows Phone имеет инструмент для имитации датчиков движения. К сожалению, поддерживается только акселерометр; для каждого другого датчика, включая OrientationSensor, вам понадобится реальное устройство.
Определение текущего оборудования
Windows Phone предлагает класс DeviceStatus
который можно использовать для получения информации о текущем устройстве, например:
- версия прошивки со свойством
DeviceFirmwareVersion
- версия аппаратного обеспечения со свойством
DeviceHardwareVersion
- производитель со свойством
DeviceManufacturer
- имя устройства со свойством
DeviceName
- объем доступной памяти с
DeviceTotalMemory
свойстваDeviceTotalMemory
Кроме того, у вас есть доступ к некоторым полезным API, чтобы узнать текущее состояние батареи. Они принадлежат пространству имен Windows.Phone.Devices.Power
, и вы сможете использовать класс Battery
для определения процента оставшегося заряда батареи (с помощью свойства RemainingChargePercent
) и оставшегося времени до полной разрядки батареи (с помощью Свойство RemainingDischargeTime
).
Класс Battery
ведет себя как датчик; вам придется использовать метод GetDefault()
чтобы получить ссылку на него (даже если в этом случае вы можете избежать проверки, является ли возвращаемый объект null
поскольку в каждом телефоне есть батарея), как в следующем примере:
1
2
3
4
5
|
private void OnGetBatteryClicked(object sender, RoutedEventArgs e)
{
int remainingCharge = Battery.GetDefault().RemainingChargePercent;
TimeSpan remainingTime = Battery.GetDefault().RemainingDischargeTime;
}
|
Кроме того, класс DeviceStatus
предлагает свойство PowerSource
, которое сообщает текущий источник питания, и обработчик событий PowerSourceChanged
, который запускается каждый раз, когда текущий источник питания изменяется (от батареи к внешнему или наоборот).
01
02
03
04
05
06
07
08
09
10
|
private void OnGetBatteryClicked(object sender, RoutedEventArgs e)
{
DeviceStatus.PowerSourceChanged += DeviceStatus_PowerSourceChanged;
}
void DeviceStatus_PowerSourceChanged(object sender, EventArgs e)
{
string message = DeviceStatus.PowerSource == PowerSource.Battery ?
MessageBox.Show(message);
}
|
Это может быть полезно, если, например, вы хотите избежать выполнения энергоемких операций, если телефон не подключен к внешнему источнику питания.
Примечание. Чтобы получить доступ к информации об оборудовании, необходимо включить функцию ID_CAP_IDENTITY_DEVICE
в файле манифеста.
близость
В категорию близости мы можем включить все новые API, которые были введены в среде выполнения Windows, чтобы соединять два устройства вместе без подключения к Интернету. В Windows Phone вы можете достичь этого результата, используя две технологии: Bluetooth и NFC.
Bluetooth хорошо известен и может использоваться для подключения устройств на расстоянии до 10 метров. Он был доступен с первого выпуска Windows Phone, но только в Windows Phone 8 были представлены API-интерфейсы, доступные для разработчиков.
NFC — более новая технология, которая начала набирать обороты в последние годы. Он может использоваться для обмена небольшими объемами данных с близкого расстояния (оба устройства должны в основном касаться друг друга). NFC — интересная технология, поскольку она работает не только с активными устройствами (например, с двумя телефонами), но и с пассивными устройствами (например, чипами, встроенными в наклейку или на странице журнала). Кроме того, Windows Phone может расширять NFC и использовать его также для создания канала связи Bluetooth без необходимости сопряжения двух устройств вручную. Таким образом, вы можете преодолеть ограничения NFC и использовать Bluetooth для передачи больших файлов данных, таких как изображения.
Примечание. Чтобы использовать API-интерфейсы Proximity, вам нужно включить опцию ID_CAP_PROXIMITY
в файле манифеста.
Самый простой способ протестировать приложения, использующие API Proximity, — на реальных устройствах, но есть также сторонний инструмент под названием Proximity Tapper, доступный в CodePlex, который можно использовать для имитации соединения между различными эмуляторами (поскольку Visual Studio может работать только один конкретный эмулятор за раз, вам придется использовать разные версии эмулятора, например WVGA и WXGA).
Обмен сообщениями
Распространенным сценарием при работе с NFC является обмен сообщениями, который представляет небольшой объем данных. Есть некоторые стандартные сообщения, которыми Windows Phone может управлять автоматически (например, когда вы получаете URI или контакт), и некоторые пользовательские сообщения, которыми могут управлять только сторонние приложения.
Первым шагом, как и с любым другим датчиком, который мы видели до этого, является использование GetDefault()
класса ProximityDevice
для получения доступа к датчику приближения. В этом случае нам также необходимо проверить, является ли задание датчика null
прежде чем двигаться дальше, поскольку некоторые устройства не поддерживают NFC.
Каждое сообщение идентифицируется определенным ключевым словом. API-интерфейсы Windows Phone изначально поддерживают три типа сообщений — текстовые, URI и двоичные. Посмотрим, как ими управлять.
Текстовые сообщения
Опубликовать текстовое сообщение очень просто. Мы используем метод PublishMessage()
класса ProximityDevice
, который требует тип сообщения и содержимое в качестве параметров. В следующем примере вы можете увидеть, как мы отправляем текстовое сообщение, определяемое ключевым словом Windows.SampleMessage
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
private void OnSendMessageClicked(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
if (device != null)
{
device.PublishMessage(«Windows.SampleMessage», «Sample message», MessageSent);
}
}
private void MessageSent(ProximityDevice sender, long messageId)
{
MessageBox.Show(«The message has been sent»);
sender.StopPublishingMessage(messageId);
}
|
Как видите, метод PublishMessage()
принимает третий необязательный параметр, который является событием, которое возникает, когда сообщение было получено другим устройством. Это событие может быть полезно, как показано в предыдущем примере, для прекращения отправки сообщения после его получения путем вызова StopPublishingMessage()
объекта ProximityDevice
. Вам необходимо установить идентификатор сообщения, который передается как параметр метода.
Телефон, получающий сообщение, должен вместо этого вызвать метод SubscribeForMessage()
. В отличие от метода публикации, этот метод одинаков независимо от ожидаемых данных. Разница в том, что, согласно сообщению, мы можем использовать некоторые специфические свойства для его анализа и извлечения необходимой нам информации.
В следующем примере вы можете увидеть, как легко извлечь содержимое сообщения благодаря DataAsString
класса ProximityMessage
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
private void OnReadMessageClicked(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
if (device != null)
{
device.SubscribeForMessage(«Windows.SampleMessage», messageReceived);
}
}
private void messageReceived(ProximityDevice sender, ProximityMessage message)
{
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(message.DataAsString);
});
sender.StopSubscribingForMessage(message.SubscriptionId);
}
|
Этот код не очень отличается от кода, используемого для отправки сообщения; метод SubscribeForMessage()
требует тип сообщения и обработчик события, который вызывается при получении сообщения.
Сообщение получено благодаря объекту ProximityMessage
который возвращается в качестве параметра. В этом случае, поскольку это текстовое сообщение, мы можем извлечь содержимое с DataAsString
свойства DataAsString
. Обратите внимание, что снова в этой ситуации мы отменяем подписку с помощью StopSubscribingForMessage()
чтобы приложение больше не прослушивало входящее сообщение.
URI
Отправка URI работает аналогичным образом, за исключением того, что нам нужно использовать метод PublishUriMessage()
который принимает Uri
для отправки в качестве параметра. В этом случае нам не нужно устанавливать тип сообщения, поскольку оно неявное.
1
2
3
4
5
6
7
8
|
private void OnPublishUriClicked(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
if (device != null)
{
device.PublishUriMessage(new Uri(«http://wp.qmatteoq.com»));
}
}
|
Разница в том, что сообщения Uri
напрямую поддерживаются Windows Phone, поэтому вы сможете обмениваться ими без использования приложения-получателя. После получения Uri
операционная система просто спросит пользователя, хочет ли он или она открыть браузер, чтобы увидеть его.
Однако, если вы все еще хотите управлять Uri
в своем приложении, вы можете подписаться на их получение. В этом случае вам придется прослушивать входящее сообщение WindowsUri
. Кроме того, вам потребуется еще немного кода для его извлечения, поскольку он не рассматривается как string
; вам нужно будет работать непосредственно с байтовым массивом, как в следующем примере:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
private void OnReceiveUriClicked(object o, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
if (device != null)
{
device.SubscribeForMessage(«WindowsUri», messageReceived);
}
}
private void messageReceived(ProximityDevice sender, ProximityMessage message)
{
byte[] array = message.Data.ToArray();
string uri = Encoding.Unicode.GetString(array, 0, array.Length);
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(uri);
});
}
|
Сообщения NDEF
NDEF (формат обмена данными NFC) — это стандартный протокол, используемый для определения сообщений NFC, которые можно использовать для обмена различными типами данных на разных платформах. Некоторыми распространенными сценариями являются обмен данными геолокации, отправка почты и совместное использование в социальных сетях.
NDEF изначально не поддерживается API-интерфейсами Windows Phone. Чтобы не требовать от разработчиков ручного создания двоичных данных, необходимых для составления сообщения, разработчик по имени Андреас Якль создал библиотеку NDEF Library For Proximity API , которая доступна в CodePlex и может быть легко установлена с помощью NuGet .
Эта библиотека содержит множество классов, которые инкапсулируют необходимую логику для наиболее распространенных сообщений, таких как NdefGeoRecord
для совместного использования координат геолокации, NdefLaunchAppRecord
для открытия другого приложения Windows Phone и NdefTelRecord
для запуска телефонного звонка на определенный номер. Вы можете увидеть полный список поддерживаемых записей на официальном сайте .
В следующем примере вы увидите, как отправить сообщение, содержащее NdefSmsRecord
которое можно использовать для создания нового SMS-сообщения с предопределенным текстом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
private void OnSendNdefMessageClicked(object sender, RoutedEventArgs e)
{
ProximityDevice device = ProximityDevice.GetDefault();
if (device != null)
{
NdefSmsRecord record = new NdefSmsRecord
{
SmsNumber = «0123456789»,
SmsBody = «This is the text»
};
NdefMessage message = new NdefMessage
{
record
};
device.PublishBinaryMessage(«NDEF», message.ToByteArray().AsBuffer());
}
}
|
Первым шагом является создание записи и установка необходимых свойств. В этом примере нам нужно установить SmsNumber
(номер телефона, который будет получать сообщение) и SmsBody
(текст сообщения).
Далее нам нужно инкапсулировать запись в новое сообщение, которое идентифицируется классом NdefMessage
. В конце концов, мы можем отправить его; в этом сценарии нам нужно использовать метод PublishBinaryMessage()
класса ProximityDevice
, поскольку это не стандартное сообщение, а двоичное. Как видите, мы передаем в качестве параметра (кроме типа сообщения, который является NDEF
) сообщение в виде NDEF
массива.
Если тип сообщения изначально поддерживается Windows Phone, устройство автоматически будет управлять входящим сообщением. В предыдущем примере операционная система предложит пользователю отправить SMS-сообщение. Вместо этого, если мы хотим получить его в приложении, нам нужно проделать дополнительную работу. Поскольку это двоичное сообщение, нам нужно извлечь необходимую информацию. К счастью, библиотека NDEF для API Proximity поможет нам, как вы можете видеть в следующем примере:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
private void OnReceiveNdefMessage( object sender, RoutedEventArgs e) {
ProximityDevice device = ProximityDevice.GetDefault(); if (device != null ) {
device.SubscribeForMessage( "NDEF" , NdefMessageReceived); }
}
private void NdefMessageReceived(ProximityDevice sender, ProximityMessage message) {
NdefMessage receivedMessage = NdefMessage.FromByteArray(message.Data.ToArray()); foreach (NdefRecord record in receivedMessage) {
if (record.CheckSpecializedType( true ) == typeof (NdefSmsRecord {
NdefSmsRecord ndefSmsRecord = new NdefSmsRecord(record); Dispatcher.BeginInvoke(() => {
MessageBox.Show(ndefSmsRecord.SmsBody); });
}
}
}
|
Нам нужно подписаться на входящие сообщения NDEF. Когда мы получаем сообщение, мы можем преобразовать двоичные данные (которые доступны в Data
свойстве ProximityMessage
класса) в NdefMessage
снова, благодаря FromByteArray()
методу.
NdefMessage
Объект может содержать более чем одну запись, так что мы должны повторять его и извлечь каждый NdefRecord
объект , который хранится. В предыдущем примере, так как мы ожидаем получить только NdefSmsRecord
, мы управляем только этим сценарием. Задача выполняется с помощью CheckSpecializedType()
метода записи, который возвращает тип данных. Мы управляем этим, только если это тот тип, который мы ожидаем. Мы можем получить исходную запись, просто создав новый NdefSmsRecord
объект и передав в качестве параметра NdefRecord
объект, сохраненный в сообщении.
Как только мы его получим, мы сможем получить доступ ко всем его свойствам. В предыдущем примере мы показываем пользователю тело сообщения SMS.
Запись сообщений в тег
Как упоминалось ранее, NFC можно использовать с пассивными устройствами, такими как теги и наклейки. Устройства Windows Phone могут записывать данные в теги NFC, просто добавляя :WriteTag
суффикс к типу сообщения при публикации сообщения.
В следующем коде вы можете увидеть, как адаптировать предыдущий пример для записи записи в тег, а не отправлять ее на другое устройство:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
private void OnSendNdefMessageClicked( object sender, RoutedEventArgs e) {
ProximityDevice device = ProximityDevice.GetDefault(); if (device != null ) {
NdefSmsRecord record = new NdefSmsRecord {
SmsNumber = "0123456789" , SmsBody = "This is the text" };
NdefMessage message = new NdefMessage {
record };
device.PublishBinaryMessage( "NDEF:WriteTag" , message.ToByteArray().AsBuffer()); }
}
|
Создание канала связи с использованием NFC
Как упоминалось ранее, NFC может использоваться только для обмена небольшими объемами данных — его нельзя использовать для поддержания стабильного канала связи. Лучшая технология для достижения этого результата — Bluetooth. Однако мы можем использовать NFC в качестве ярлыка для установления канала Bluetooth между двумя устройствами, на которых установлено одно и то же приложение.
Примечание. В этом случае нам также необходимо включить ID_CAP_NETWORKING
параметр в файле манифеста.
Отправной точкой является PeerFinder
класс, который можно использовать (на обоих устройствах) для запуска соединения и поиска другого устройства для сопряжения. Использовать его довольно просто: вы должны подписаться на TriggeredConnectionStateChanged
событие, которое срабатывает при изменении состояния соединения, и запустить процесс сопряжения, вызвав Start()
метод.
1
2
3
4
5
|
private async void OnConnectToPeerClicked( object sender, RoutedEventArgs e) {
PeerFinder.TriggeredConnectionStateChanged += PeerFinder_TriggeredConnectionStateChanged; PeerFinder.Start(); }
|
Вам нужно будет использовать один и тот же код на обоих устройствах. Тем не менее, есть обходной путь для его автоматического запуска в случае, если приложение открывается по запросу на сопряжение. Фактически, когда вы выполняете предыдущий код и соединяете два устройства вместе, Windows Phone автоматически перехватит входящее сообщение и предложит пользователю открыть требуемое приложение. Когда это происходит, приложение открывается со следующим специальным URI:
1
|
MainPage.xaml?ms_nfp_launchargs=Windows.Networking.Proximity.PeerFinder: StreamSocket |
Используя OnNavigatedTo
событие на главной странице, мы можем перехватить этот специальный URI и автоматически запустить процесс сопряжения, как показано в следующем примере:
01
02
03
04
05
06
07
08
09
10
|
protected override void OnNavigatedTo(NavigationEventArgs e) {
if (NavigationContext.QueryString.ContainsKey( "ms_nfp_launchargs" ) && NavigationContext.QueryString[ "ms_nfp_launchargs" ] == "Windows.Networking.Proximity.PeerFinder:StreamSocket" ) {
PeerFinder.TriggeredConnectionStateChanged += PeerFinder_TriggeredConnectionStateChanged; PeerFinder.Start(); }
}
|
TriggeredConnectionStateChanged
Событие может быть использовано для управления фактического состояния соединения. Наиболее важным является состояние Completed
, которое срабатывает, когда соединение успешно установлено, и вы можете начать обмен данными.
В следующем примере вы можете видеть, что, как только соединение установлено, мы сохраняем канал (идентифицируемый StreamSocket
объектом) в другой переменной, которая будет использоваться позже для дальнейшей связи.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
private StreamSocket socket; void PeerFinder_TriggeredConnectionStateChanged( object sender, TriggeredConnectionStateChangedEventArgs args) {
switch (args.State) {
case TriggeredConnectState.Completed: socket = args.Socket; StartListeningForMessages(); PeerFinder.Stop(); break;
}
}
|
Помимо сохранения ссылки на канал, мы начинаем прослушивать входящие сообщения (посмотрим, как это будет через мгновение) и вызываем Stop()
метод PeerFinder
класса. Так как канал был создан, мы можем остановить процесс сопряжения.
Прослушивание входящих сообщений
Процесс прослушивания работает аналогично опросу; пока канал не будет открыт, мы продолжаем спрашивать другое устройство, есть ли новое сообщение. В следующем примере мы обмениваемся текстовыми сообщениями с помощью классов DataReader
и, DataWriter
которые мы научились использовать ранее в этой серии в контексте хранения данных в локальном хранилище.
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
39
40
41
42
|
private bool listening; private async void StartListeningForMessages() {
if (socket != null ) {
if (!listening) {
listening = true ; while (listening) {
var message = await GetMessage(); if (listening) {
if (message != null ) {
Dispatcher.BeginInvoke(() => MessageBox.Show(message)); }
}
}
}
}
}
private async Task< string > GetMessage() {
DataReader dataReader = new DataReader(socket.InputStream); uint bytesRead = await dataReader.LoadAsync( sizeof ( uint )); if (bytesRead > 0) {
uint strLength = ( uint )dataReader.ReadUInt32(); bytesRead = await dataReader.LoadAsync(strLength); if (bytesRead > 0) {
String message = dataReader.ReadString(strLength); return message; }
}
return string .Empty; }
|
Пока канал открыт и процесс прослушивания активен, мы продолжаем вызывать GetMessage()
метод. Если в канале есть входящее сообщение, мы показываем его пользователю. Процедура опроса реализована с использованием while
цикла, который повторяется до тех пор, пока для isListening
переменной не установлено значение true
.
GetMessage()
Метод просто помощник , который, используя DataReader
класс, может получить данные на этом канале (который хранятся в двоичном виде в InputStream
свойстве StreamSocket
класса) и превратить его в простую строку.
Отправка сообщения
Чтобы отправить сообщение, нам нужно использовать DataWriter
класс для записи данных в OutputStream
канал StreamSocket
объекта. Мы должны отправить две части информации: размер сообщения, используя WriteInt32()
метод, и текст сообщения, используя WriteString()
сообщение.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public async Task SendMessage(string message) {
if (socket != null) {
DataWriter dataWriter = new DataWriter(socket.OutputStream); dataWriter.WriteInt32(message.Length); await dataWriter.StoreAsync(); dataWriter.WriteString(message); await dataWriter.StoreAsync(); }
}
|
Если вы хотите отправить сообщение на канал, достаточно использовать SendMessage()
метод, который мы только что определили:
1
2
3
4
|
private async void OnSendMessageOnChannelClicked(object sender, RoutedEventArgs e) {
await SendMessage(“This is my first message”); }
|
Создание канала связи с использованием Bluetooth
Подход к созданию канала связи с использованием Bluetooth тот же, что мы видели ранее. Канал определяется StreamSocket
классом, и мы можем отправлять и прослушивать входящие сообщения точно таким же образом.
Что меняется, так это способ подключения к другому устройству. При использовании NFC одноранговая связь обязательно осуществляется между двумя подключенными устройствами. С помощью Bluetooth вы можете виртуально подключиться к любому устройству в зоне действия вашего телефона.
Мы собираемся снова использовать PeerFinder
класс, но на этот раз вместо запуска процесса сопряжения с использованием Start()
метода мы ищем все доступные одноранговые узлы с помощью FindAllPeersAsync()
метода. Он возвращает коллекцию PeerInformation
объектов — каждый из них является устройством, которое может подключаться к нашему приложению.
В следующем примере мы просто отображаем список доступных устройств для пользователя, устанавливая коллекцию как ItemsSource
элемент ListBox
управления:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
private async void OnFindNearPeers(object sender, RoutedEventArgs e) {
PeerFinder.Start(); try
{
IReadOnlyList<PeerInformation> peers = await PeerFinder.FindAllPeersAsync(); PeersList.ItemsSource = peers; }
catch (Exception exc) {
if ((uint)exc.HResult == 0x8007048F) {
MessageBox.Show(“Bluetooth is turned off”); }
}
}
|
Обратите внимание, что мы встроили наш код в оператор try
/ catch
; фактически пользователь может отключить Bluetooth. Хотя мы начинаем искать других пиров, если мы получаем исключение с кодом ошибки 0x8008048F
, это означает, что мы находимся в этой ситуации, поэтому мы должны правильно управлять этим (например, сообщая пользователю, что ему или ей нужно включить его). на использование приложения).
После того, как пользователь выбрал, к какому устройству он или она хочет подключиться, нам нужно вызвать ConnectAsync()
метод PeerFinder
класса, передав в PeerInformation
качестве параметра объект, представляющий устройство. Далее, точно так же, как мы это делали для связи NFC, мы начинаем прослушивать сообщения и прекращаем поиск других пиров, используя Stop()
метод, как показано в следующем примере:
1
2
3
4
5
6
7
|
private async void PeerList_OnSelectionChanged(object sender, SelectionChangedEventArgs e) {
PeerInformation peer = PeerList.SelectedItem as PeerInformation; socket = await PeerFinder.ConnectAsync(peer); StartListeningForMessages(); PeerFinder.Stop(); }
|
Этот StartListeningForMessage()
метод тот же, что мы использовали для связи с NFC
С другой стороны (телефон, с которым пользователь выбрал взаимодействие), если мы хотим принять входящий запрос связи с другого телефона, нам нужно снова использовать Start()
метод PeerFinder
класса и подписаться на ConnectionRequested
событие, которое срабатывает, когда другое устройство запросило соединение.
В обработчике событий нам просто нужно вызвать ConnectAsync()
метод PeerFinder
класса, как мы это сделали с телефоном, который запустил запрос на соединение. Мы получим ссылку на устройство, которое отправило запрос в параметрах метода.
01
02
03
04
05
06
07
08
09
10
11
12
|
private void OnListenToConnectionClicked(object sender, RoutedEventArgs e) {
PeerFinder.ConnectionRequested += PeerFinder_ConnectionRequested; PeerFinder.Start(); }
async void PeerFinder_ConnectionRequested(object sender, ConnectionRequestedEventArgs args) {
socket = await PeerFinder.ConnectAsync(args.PeerInformation); StartListeningForMessages(); PeerFinder.Stop(); }
|
Вывод
В этой статье мы увидели, как взаимодействовать с аппаратными функциями, которыми обладает каждое устройство Windows Phone. В частности:
- Мы увидели, как взаимодействовать со службами геолокации, получать местоположение пользователя и взаимодействовать с элементом
Map
управления. - Мы узнали, как использовать датчики движения для определения положения устройства в пространстве. Эта функция особенно полезна в играх, так как многие из них легче контролировать с помощью акселерометра, а не с помощью виртуального управления.
- Мы кратко рассмотрели аппаратные API, которые полезны для получения информации об устройстве, на котором работает приложение.
- Мы обсудили API-интерфейсы Proximity, которые были представлены в среде выполнения Windows для соединения двух устройств без подключения к Интернету. В частности, мы говорили о двух технологиях: NFC и Bluetooth.