Зачем?
В начале этого года я написал в блоге о TtcSocketHelper , дружественном MVVM-объекте, который позволял вам соединять два устройства Windows Phone 8 друг с другом с помощью сокета. Я сделал с ним две игры — Pull the Rope и 2 Phone Pong , стиль «один на один». игры, в которые играют на двух телефонах, обмен данными JSON для синхронизации. За последние 10 месяцев произошло много событий: на рынок вышли очень дешевые устройства, прежде всего Nokia Lumia 520 . Это полноценный Windows Phone 8, но он очень дешевый и, как следствие, он не поддерживает NFC (и, следовательно, не нажмите + отправить). По-видимому, это не касается покупателей, так как согласно AdDuplex Статистика взлетела, как летучая мышь из ада. Всего за шесть месяцев он занял почти 33% рынка Windows Phone 8. Другими словами, ограничивая свое приложение телефонами с поддержкой NFC, я отрезал себя от значительной — и растущей — части рынка.
PhonePairConnectionHelper — поддержка просмотра по Bluetooth.
Итак, я переписал TtcSocketHelper в PhonePairConnectionHelper — в основном того же класса, но теперь он поддерживает подключение телефонов с использованием tap + send, а также с использованием проверенного и проверенного способа сопряжения Bluetooth. С 21 октября в магазине появился 2 Phone Pong 1.3.0 , гордо работающий на этом компоненте, и теперь в мою игру могут играть на 50% больше людей!
В этой статье я собираюсь сосредоточиться на подключении через Bluetooth, так как подключение через tap + send уже описано в моей статье с января 2013 года.
Начало объекта снова достаточно простое и на самом деле очень похоже на предыдущее воплощение:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; using GalaSoft.MvvmLight.Messaging; using Windows.Foundation; using Windows.Networking.Proximity; using Windows.Networking.Sockets; using Windows.Storage.Streams; namespace Wp7nl.Devices { public class PhonePairConnectionHelper { private ConnectMethod connectMode; public PhonePairConnectionHelper() { Messenger.Default.Register<NavigationMessage>(this, ProcessNavigationMessage); PeerFinder.TriggeredConnectionStateChanged += PeerFinderTriggeredConnectionStateChanged; PeerFinder.ConnectionRequested += PeerFinderConnectionRequested; } private void ProcessNavigationMessage(NavigationMessage message) { Debug.WriteLine("PhonePairConnectionHelper.ProcessNavigationMessage " + message.NavigationEvent.Uri); if (message.IsStartedByNfcRequest) { Start(); } } } }
Наиболее примечательным является то, что сейчас настройка выполняется в конструкторе. Класс все еще слушает NavigationMessage и, если так, он все еще вызывает метод start (это для маршрута tap + send, который все еще работает). PeerFinder настроен на прослушивание событий TriggeredConnecState (т. Е. Событий, которые происходят после нажатия + отправки), а также — и это важно — для простых запросов Bluetooth (событие ConnectionRequested). Метод запуска сейчас сильно изменился:
public void Start(ConnectMethod connectMethod = ConnectMethod.Tap, string displayAdvertiseName = null) { PeerFinder.Stop(); connectMode = connectMethod; if (!string.IsNullOrEmpty(displayAdvertiseName)) { PeerFinder.DisplayName = displayAdvertiseName; } PeerFinder.AllowBluetooth = true; PeerFinder.Start(); // Enable browse if (connectMode == ConnectMethod.Browse) { PeerFinder.FindAllPeersAsync().AsTask(). ContinueWith(p => FirePeersFound(p.Result)); } }
Перечисление ConnectMethod, как вы уже можете видеть из кода, позволяет указать, какой метод вы хотите использовать:
namespace Wp7nl.Devices { public enum ConnectMethod { Tap, Browse } }
Метод Start имеет необязательный параметр, который дает вам возможность выбрать метод подключения и при необходимости добавить отображаемое имя. Отображаемое имя применимо только для метода Bluetooth. Интересно то, что если вы не предоставите имя, как я сделал в примере, он будет просто использовать имя телефона, как вы его предоставили (давая ему имя через Проводник, если вы подключаетесь через USB-кабель к ваш компьютер) — сокращенно до 15 символов. Почему это только 15 символов — ваше предположение так же хорошо, как и мое.
Если вы вызываете этот метод без каких-либо параметров, он использует значения по умолчанию и попытается подключиться, используя tap + send, как и раньше.
Прежде чем выбрать метод, вы можете проверить, какие методы подключения поддерживает устройство, используя два вспомогательных свойства объекта:
public bool SupportsTap { get { return (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Triggered) == PeerDiscoveryTypes.Triggered; } } public bool SupportsBrowse { get { return (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Browse) == PeerDiscoveryTypes.Browse; } }
Имейте в виду — помощник не настолько умен, что отказывается подключаться в неподдерживаемом режиме. И я не проверяю это в примере приложения. Это зависит от вашего приложения. Но в любом случае — отступай немного. Если вы попытаетесь подключиться через Bluetooth, компонент попытается найти все одноранговые узлы, то есть другие телефоны, на которых запущено то же приложение, с помощью FindAllPeersAsync, и после этого вызовет событие PeersFound:
private void FirePeersFound(IEnumerable<PeerInformation> args) { if (PeersFound != null) { PeersFound(this, args); } } public event TypedEventHandler<object, IEnumerable<PeerInformation>> PeersFound;
Ваше приложение должно подписаться на это событие, а затем оно получает список объектов «PeerInformation». И PeerEvent имеет ряд свойств, но вам нужно иметь дело только с одним свойством DisplayName. Вы отображаете это в списке, раскрывающемся списке или в любом другом приложении, и после того, как пользователь сделал выбор, вы вызываете метод Connect:
public void Connect(PeerInformation peerInformation) { DoConnect(peerInformation); } private void DoConnect(PeerInformation peerInformation) { PeerFinder.ConnectAsync(peerInformation).AsTask().ContinueWith(p => { socket = p.Result; StartListeningForMessages(); PeerFinder.Stop(); FireConnectionStatusChanged(TriggeredConnectState.Completed); }); }
И бум. У вас есть подключение к телефону, который вы выбрали. Единственное, чего сейчас не хватает, так это того, что телефон, к которому вы подключились, еще не подключен к вам . Но когда вы установили соединение, PeerFinder запустил событие ConnectionRequested. Помните, мы настроили прослушиватель «PeerFinderConnectionRequested» для этого в конструкторе? Ну думаю , что часть полезной нагрузки аргументов этого метода события получает является объектом PeerInformation а , содержащий информацию для подключения задней к телефону , который просто подключен к вам :-). Что делает подключение довольно простым:
private void PeerFinderConnectionRequested(object sender, ConnectionRequestedEventArgs args) { if (connectMode == ConnectMethod.Browse) { DoConnect(args.PeerInformation); } }
И сделано. Мы знаем, что у нас есть двустороннее сокетное соединение с использованием Bluetooth, и мы можем вызвать метод SendMessage, как и раньше, и прослушать результат, подписавшись на событие MessageReceived. Единственное, что я нарушил в интерфейсе этого помощника по отношению к предшественнику, — это тип события ConnectionStatusChanged. Раньше это было
public event TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs> ConnectionStatusChanged;
и сейчас
public event TypedEventHandler<object, TriggeredConnectState> ConnectionStatusChanged;
Так что, если вы подписываетесь на это событие, вам не нужно проверять args.State, а только само args. Это было необходимо, чтобы иметь возможность запускать события подключения из DoConnect.
Остальная часть класса почти идентична старому TtcSocketHelper, и я не буду повторяться здесь.
Чтобы использовать этот класс
Сначала, решите, хотите ли вы использовать маршрут Tap + Send или маршрут Bluetooth. Мои приложения проверяют свойства SupportsTap и SupportsBrowse. Если поддерживается NFC (то есть нажатие + отправка), я предлагаю оба варианта и предоставляю пользователям выбор способа подключения. Если не поддерживается, я предлагаю только Bluetooth.
Для маршрута NFC путь по-прежнему:
- Убедитесь, что ваше приложение запускает NavigationMessage, как описано здесь
- Создайте новый PhonePairConnectionHelper.
- Подписаться на его событие ConnectionStatusChanged
- Подпишитесь на событие MessageReceived
- Call Start
- Подождите, пока не появится TriggeredConnectState.Completed
- Вызовите SendMessage — и увидите, что они появляются в методе, подписанном на MessageReceived на другом телефоне.
Способ использования Bluetooth:
- Создайте новый PhonePairConnectionHelper.
- Подписаться на его событие ConnectionStatusChanged Подписаться на его событие MessageReceived
- Подписаться на его PeersFoundevent
- Вызовите Start с ConnectMethod.Browse и отображаемым именем.
- Подождите, пока не произойдет событие PeersFound
- Выберите одноранговый узел для подключения в вашем приложении
- Вызовите метод Connect с выбранным узлом
- Подождите, пока не появится TriggeredConnectState.Completed
- Вызовите SendMessage — и увидите, что они появляются в методе, подписанном на MessageReceived на другом телефоне.
Так что, в принципе, нет особой разницы ?
Изменения в оригинальном образце
Самое интересное, что вам даже не нужно сильно менять существующее демонстрационное решение . Я добавил в viewmodel следующий код:
#region Bluetooth stuff private bool useBlueTooth; public bool UseBlueTooth { get { return useBlueTooth; } set { if (useBlueTooth != value) { useBlueTooth = value; RaisePropertyChanged(() => UseBlueTooth); } } } private PeerInformation selectedPeer; public PeerInformation SelectedPeer { get { return selectedPeer; } set { if (selectedPeer != value) { selectedPeer = value; RaisePropertyChanged(() => SelectedPeer); } } } public ObservableCollection<PeerInformation> Peers { get; private set; } private void PeersFound(object sender, IEnumerable<PeerInformation> args) { Deployment.Current.Dispatcher.BeginInvoke(() => { Peers.Clear(); args.ForEach(Peers.Add); if (Peers.Count > 0) { SelectedPeer = Peers.First(); } else { ConnectMessages.Add("No contacts found"); } }); } public ICommand ConnectBluetoothContactCommand { get { return new RelayCommand(() => { connectHelper.Connect(SelectedPeer); Peers.Clear(); }); } } #endregion
В принципе:
- Свойство, чтобы определить, используете ли вы Bluetooth или нет (не = нажмите + отправить)
- Собственность, держащая выбранного пира
- Список доступных пиров
- Обратный вызов для события помощника PeersFound. Не то, чтобы все здесь происходило в Диспетчере. Поскольку событие PeersFound возвращается из фонового потока, оно не имеет доступа к пользовательскому интерфейсу, но, поскольку оно обновляет несколько связанных свойств, ему необходим такой доступ — отсюда и Dispatcher.
- и команда, позволяющая пользователю выбрать определенного партнера. Обратите внимание на Peers.Clear после того, как Peer выбран. Это потому, что я использую свой HideWhenCollectionEmptyBehavior для отображения пользовательского интерфейса для отображения и выбора пиров. Это поведение в примере решения в виде кода, так как я забыл добавить его в последний выпуск моей библиотеки wp7nl на codeplex . * Ahem *
Два небольших дополнения к остальной части модели представления: в методе Init, где я настроил все connectHelper, теперь он говорит:
connectHelper = new PhonePairConnectionHelper(); connectHelper.ConnectionStatusChanged += ConnectionStatusChanged; connectHelper.MessageReceived += TtsHelperMessageReceived; connectHelper.PeersFound += PeersFound; // Added for Bluetooth support
И есть также небольшое изменение в StartCommand, чтобы учесть тот факт, что если пользователь выбирает Bluetooth, connectHelper должен вызываться немного по-другому.
public ICommand StartCommmand { get { return new RelayCommand( () => { ConnectMessages.Add("Connect started..."); CanSend = false; CanInitiateConnect = false; // Changed for Bluetooth. if(UseBlueTooth) { connectHelper.Start(ConnectMethod.Browse); } else { connectHelper.Start(); } }); } }
Чтобы пользователь мог фактически выбрать способ подключения, начальный экран немного изменился и теперь содержит две кнопки-переключателя. Bluetooth выбран по умолчанию.
<RadioButton Content="tap+send" IsChecked="{Binding UseBlueTooth, Converter={StaticResource BooleanInvertConvertor}, Mode=TwoWay}" IsEnabled="{Binding CanInitiateConnect, Mode=OneWay}" /> <RadioButton Content="bluetooth" IsChecked="{Binding UseBlueTooth, Mode=TwoWay}" HorizontalAlignment="Right" Margin="0" IsEnabled="{Binding CanInitiateConnect, Mode=OneWay}" /> which is not quite rocket science, and some more code to give a user a simple UI to view and select found peers: <Grid x:Name="bluetoothconnectgrid" VerticalAlignment="Bottom"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <i:Interaction.Behaviors> <behaviors:HideWhenCollectionEmptyBehavior Collection="{Binding Peers}"/> </i:Interaction.Behaviors> <toolkit:ListPicker x:Name="OpponentsListPicker" Margin="12,0" VerticalAlignment="Top" ExpansionMode="FullScreenOnly" ItemsSource="{Binding Peers}" SelectedItem="{Binding SelectedPeer, Mode=TwoWay}" /> <Button Content="select contact" Height="72" Grid.Row="1" Command="{Binding ConnectBluetoothContactCommand, Mode=OneWay}"/> </Grid>
Также не очень сложный — средство выбора списка отображает одноранговые узлы и выбирает одноранговый узел, а кнопка запускает команду для фактического соединения. Мне скорее нравится использование HideWhenCollectionEmptyBehavior здесь — это автоматически показывает этот кусок пользовательского интерфейса, поскольку в списке есть одноранговые узлы, и скрывает его, когда их нет. Это довольно элегантно, если можно так выразиться. На картинке справа вы можете увидеть название моего 920 — сокращенно до 15 символов.
После того, как соединение было установлено — либо через Bluetooth, либо нажатием + отправить — вы можете использовать это приложение для чата, как и в предыдущей версии.
Важные вещи, которые нужно знать
- Если вы разработчик, который хочет защитить своих клиентов от разочарований, вы отметили «NFC» как требование в файле WMAppManifest.xml, когда создавали приложение поверх моего предыдущего TtcSocketHelper. Уберите галочку прямо сейчас . Или же ваше приложение все еще не будет доступно для телефонов, таких как 520, которые не имеют NFC. И это совершенно не соответствует сути всего этого нового класса.
- Я обнаружил, что при просмотре Bluetooth в Windows Phone 8 необходимо учитывать некоторые особенности
- Вы все еще должны быть достаточно близко друг к другу — где-то в пределах метра — чтобы найти пиров. Как только соединение установлено, вы можете двигаться дальше друг от друга
- Первый телефон, который начинает поиск пира, обычно ничего не находит. Второй находит одного противника, третий — двух. Процесс поиска обычно не длится очень долго — в большинстве случаев всего несколько секунд — до того, как PeerFinder либо сдается, либо возвращается с одноранговым узлом. Желательно не вызывать Connect автоматически, если вы нашли только одного пира — это может быть не тот, кого искал пользователь. Всегда держите пользователя под контролем.
- Если вы подключаетесь через касание + отправка, вам нужно будет только запустить приложение на одном из телефонов и нажать кнопку подключения — другой телефон автоматически запустит приложение (или даже загрузит его, если его там нет), когда устройства связаны между собой.
- Для маршрута Bluetooth приложение должно быть запущено на обоих телефонах, и на обоих телефонах вам нужно будет нажать кнопку подключения. Заключение
Рынок приложений постоянно меняется, это справедливо для всех платформ, но это точно относится и к Windows Phone. Важно быть в курсе этих событий. На рынке Windows Phone наблюдается значительный рост, особенно на нижних моделях телефонов. Важно пройти лишнюю милю для этих групп — обслуживайте как можно больше пользователей. Надеюсь, я немного помог вам с этим, как на общем примере, так и на реальном коде.
Демо-решение, как всегда, можно скачать здесь .