Статьи

Windows Phone 8: кратко: интеграция с оборудованием

В этой статье мы рассмотрим, как вы можете использовать аппаратные возможности устройства 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 работает аналогичным образом, за исключением того, что нам нужно использовать метод 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 (формат обмена данными 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 может использоваться только для обмена небольшими объемами данных — его нельзя использовать для поддержания стабильного канала связи. Лучшая технология для достижения этого результата — 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 тот же, что мы видели ранее. Канал определяется 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.