开发者可以利用蓝牙的相关API来创建应用程序,在应用程序里面使用手机的蓝牙技术来进行近距离的文件传输和发送接收消息,创造出更加有趣和方便的应用软件。
在Windows Phone 8里面可以在应用程序里面利用蓝牙进行通信,使用蓝牙相关的API,可以让应用程序连接到另外的一个应用程序,也可以让应用程序连接到一个设备上。Windows Phone 8的蓝牙技术支持两个蓝牙方案:一个是应用程序到应用程序的通信,另外一个是应用程序到设备的通信。
1.应用程序到应用程序的通信
应用程序到应用程序的通信的过程是,应用程序使用蓝牙去查找正在广播蓝牙服务的对等的应用程序,如果在应用程序提供服务的范围内发现一个应用程序,那么该应用程序可以发起连接请求。当这两个应用程序接受连接,它们之间就可以进行通信了,通信的过程是使用socket的消息发送接收机制。在Windows Phone 8中使用到应用程序到应用程序的蓝牙通讯技术,需要在项目的WMAppManifest.xml文件中添加ID_CAP_PROXIMITY的功能选项,表示支持临近的设备通信能力,否则程序会出现异常。
2.应用程序到设备的通信
在应用程序到设备的通信过程时,应用程序使用蓝牙去查找提供服务的设备,如果提供的服务范围之内发现一个可以连接的蓝牙设备,那么该应用程序可以发起连接请求。当应用程序和设备同时接受该连接,它们之间就可以进行通信了,通信的过程也是使用socket的消息发送接收机制,类似于应用程序到应用程序的通信。在Windows Phone 8中使用到应用程序到设备的蓝牙通讯技术,需要在项目的WMAppManifest.xml文件中添加ID_CAP_PROXIMITY和ID_CAP_NETWORKING的功能选项,表示支持临近的设备通信能力和网络通信能力,否则程序会出现异常。
蓝牙编程类
在Windows Phone 8里面使用到蓝牙编程主要会用到PeerFinder类,PeerInformation类,StreamSocket类和ConnectionRequestedEventArgs类,这些类的说明如表19.1所示。因为蓝牙也是基于TCP协议进行消息传递了,所以需要用到Socket的相关的编程知识,以及StreamSocket类。PeerFinder类是蓝牙查找类,它的主要成员如表19.2所示。
表19.1 蓝牙编程类的说明
类名 说明
PeerFinder
用于去查找附近的设备是否有运行和当前应用程序相同的应用程序,并且可以在两个应用程序之间建立起socket连接,从而可以进行通信。对等应用程序是在其他设备上运行的应用程序的另一个实例。
PeerInformation
包含对等应用程序或设备的识别信息。
StreamSocket
支持使用一个TCP的Socket流的网络通信。
ConnectionRequestedEventArgs
表示传递到一个应用程序的ConnectionRequested事件的属性
表 19.2 PeerFinder类的成员
成员 说明
bool AllowBluetooth
指定 PeerFinder 类的此实例是否可以通过使用 Bluetooth 来连接 ProximityStreamSocket 对象。如果PeerFinder 的此实例可以通过使用 Bluetooth 来连接 ProximityStreamSocket 对象,则为 true;否则为false。默认为 true。
bool AllowInfrastructure
是否使用TCP/IP协议连接到StreamSocket
bool AllowWiFiDirect
指定 PeerFinder 类的此实例是否可以通过使用 Wi-Fi Direct 来连接 ProximityStreamSocket 对象。如果 PeerFinder 的此实例可以通过使用 Wi-Fi Direct 来连接 ProximityStreamSocket 对象,则为 true;否则为false。默认为 true。
IDictionary<string, string> AlternateIdentities
获取要与其他平台上的对等应用程序匹配的备用 AppId 值列表。返回要与其他平台的对等类应用程序匹配的备用 AppId 值列表。
string DisplayName
获取或设置标识计算机到远程对等类的名称。
PeerDiscoveryTypes SupportedDiscoveryTypes
获取一个值,该值指示哪些发现选项可与 PeerFinder 类一同使用
event TypedEventHandler<object, ConnectionRequestedEventArgs> ConnectionRequested
远程对等类使用 ConnectAsync 方法请求连接时发生。
event TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs> TriggeredConnectionStateChanged
在远程对等类的轻击笔势期间发生。
IAsyncOperation< StreamSocket> ConnectAsync(PeerInformation peerInformation)
连接已发现了对 FindAllPeersAsync 方法的调用的对等类。peerInformation:表示连接到的对等类的对等类信息对象。返回通过使用所提供的临近StreamSocket 对象连接远程对等类的异步操作。
IAsyncOperation<IReadOnlyList<PeerInformation>> FindAllPeersAsync()
适用于无线范围内运行相同应用程序的对等计算机的异步浏览。返回通过使用 Wi-Fi直连技术浏览对等类的异步操作。
void Start(string peerMessage)
向临近设备上的对等类应用程序传递消息。
void Stop()
停止查找对等类应用程序或广播对等类连接的过程
查找蓝牙设备和对等项
查找在服务范围内的蓝牙设备和对等项是蓝牙编程的第一步,查找蓝牙设备和对等项中会使用到PeerFinder类的FindAllPeersAsync方法去进行查找,然后以异步的方式返回查找到的对等项列表的信息IReadOnlyList<PeerInformation>,注意要使查找对等的应用程序时,在调用FindAllPeersAsync方法前必须先调用PeerFinder类的Start方法,主要的目的是启动广播服务,让对方的应用程序也能查找到自己。PeerInformation包含三个属性:一个是DisplayName表示对等项的名字,这个名字一般都是由对方的设备的名称或者查找到的应用程序自身设置的现实名字,一个是HostName表示主机名字或者IP地址,还有一个属性是ServiceName表示服务名称或者TCP协议的端口号。然后可以利用查找到的PeerInformation信息进行连接和通信。
查找对等的应用程序的代码示例:
async void AppToApp()
{
// 启动查找服务
PeerFinder.Start();
//开始查找
ObservableCollection<PeerInformation> peers = await PeerFinder.FindAllPeersAsync();
if (peers.Count == 0)
{
//未找到任何的对等项
}
else
{
//处理查找到的对等项,可以使用PeerFinder类的ConnectAsync方法来连接选择的要进行通信的对等项
}
}
查找蓝牙设备的代码示例:
private async void AppToDevice()
{
// 设置查找所匹配的蓝牙设备
PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";
// 开始查找
ObservableCollection<PeerInformation> pairedDevices = await PeerFinder.FindAllPeersAsync();
if (pairedDevices.Count == 0)
{
// 没有找到可用的蓝牙设备
}
else
{
//处理查找到的蓝牙设备,可以新建一个StreamSocket对象,然后使用StreamSocket类的ConnectAsync方法通过HostName和ServiceName来连接蓝牙设备
}
}
蓝牙发送消息
蓝牙编程的发送消息机制使用的是TCP的StreamSocket的方式,原理与Socket的一致。在蓝牙连接成功后,可以获取到一个StreamSocket类的对象,然后我们使用该对象的OutputStream属性来初始化一个DataWriter对象,通过DataWriter对象来进行发送消息。OutputStream属性表示的是Socket的输出流,用于发送消息给对方。下面来看一下发送消息的示例:
async void SendMessage(string message)
{
// 连接选中的对等项,selectedPeer为查找到的PeerInformation对象
StreamSocket _socket= = await PeerFinder.ConnectAsync(selectedPeer);
// 创建DataWriter
DataWriter _dataWriter = new DataWriter(_socket.OutputStream);
// 先写入发送消息的长度
_dataWriter.WriteInt32(message.Length);
await _dataWriter.StoreAsync();
// 最后写入发送消息的内容
_dataWriter.WriteString(message);
await _dataWriter.StoreAsync();
}
蓝牙接收消息
蓝牙编程的接收消息机制同样也是使用的是TCP的StreamSocket的方式,原理与Socket的一致。在蓝牙连接成功后,可以获取到一个StreamSocket类的对象,然后我们使用该对象的InputStream属性来初始化一个DataReader对象,通过DataReader对象来进行接收消息。InputStream属性表示的是Socket的输入流,用于接收对方的消息。下面来看一下接收消息的示例:
async Task<string> GetMessage()
{
// 连接选中的对等项,selectedPeer为查找到的PeerInformation对象
StreamSocket _socket= = await PeerFinder.ConnectAsync(selectedPeer);
// 创建DataReader
DataReader _dataReader = new DataReader(_socket.InputStream);
// 先读取消息的长度
await _dataReader.LoadAsync(4);
uint messageLen = (uint)_dataReader.ReadInt32();
// 最后读取消息的内容
await _dataReader.LoadAsync(messageLen);
return _dataReader.ReadString(messageLen);
}
实例:实现蓝牙程序对程序的传输
下面给出蓝牙程序对程序传输的示例:通过使用蓝牙功能查找周边也要使用改应用的手机,互相建立起连接和发送测试消息。
代码清单19-1:蓝牙程序对程序传输(源代码:第19章\Examples_19_1)
MainPage.xaml文件主要代码
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<Button x:Name="btFindBluetooth" Content="通过蓝牙查找该应用设备" Click="btFindBluetooth_Click"/>
<ListBox x:Name="lbBluetoothApp" ItemsSource="{Binding}" >
<ListBox.ItemTemplate >
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding DisplayName}" />
<TextBlock Text="{Binding ServiceName}" />
<Button Content="连接" HorizontalAlignment="Left" Width="308" Height="91" Click="btConnect_Click"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
MainPage.xaml.cs文件主要代码
using System;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
namespace BluetoothDemo
{
public partial class MainPage : PhoneApplicationPage
{
private StreamSocket _socket = null; // Socket数据流对象
private DataWriter _dataWriter; // 数据写入对象
private DataReader _dataReader; // 数据读取对象
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;//页面加载事件
}
// 查找蓝牙对等项按钮事件处理
private async void btFindBluetooth_Click(object sender, RoutedEventArgs e)
{
try
{
//开始查找对等项
PeerFinder.Start();
// 等待找到的对等项
var peers = await PeerFinder.FindAllPeersAsync();
if (peers.Count == 0)
{
MessageBox.Show("没有发现对等的蓝牙应用");
}
else
{
// 把对等项目绑定到列表中
lbBluetoothApp.ItemsSource = peers;
}
}
catch(Exception ex)
{
if ((uint)ex.HResult == 0x8007048F)
{
MessageBox.Show("Bluetooth已关闭请打开手机的蓝牙开关");
}
else
{
MessageBox.Show(ex.Message);
}
}
}
// 连接蓝牙对等项的按钮事件处理
private async void btConnect_Click(object sender, RoutedEventArgs e)
{
Button deleteButton = sender as Button;
PeerInformation selectedPeer = deleteButton.DataContext as PeerInformation;
// 连接到选择的对等项
_socket = await PeerFinder.ConnectAsync(selectedPeer);
// 使用输出输入流建立数据读写对象
_dataReader = new DataReader(_socket.InputStream);
_dataWriter = new DataWriter(_socket.OutputStream);
// 开始读取消息
PeerFinder_StartReader();
}
// 读取消息
async void PeerFinder_StartReader()
{
try
{
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);
MessageBox.Show("获取到消息:" + message);
// 开始下一条消息读取
PeerFinder_StartReader();
}
else
{
MessageBox.Show("对方已关闭连接");
}
}
else
{
MessageBox.Show("对方已关闭连接");
}
}
catch (Exception e)
{
MessageBox.Show("读取失败: " + e.Message);
}
}
// 页面加载事件处理
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// 订阅连接请求事件
PeerFinder.ConnectionRequested += PeerFinder_ConnectionRequested;
}
// 连接请求事件处理
void PeerFinder_ConnectionRequested(object sender, ConnectionRequestedEventArgs args)
{
// 连接并且发送消息
ConnectToPeer(args.PeerInformation);
}
// 连接并发送消息给对方
async void ConnectToPeer(PeerInformation peer)
{
_socket = await PeerFinder.ConnectAsync(peer);
_dataReader = new DataReader(_socket.InputStream);
_dataWriter = new DataWriter(_socket.OutputStream);
string message = "测试消息";
uint strLength = _dataWriter.MeasureString(message);
_dataWriter.WriteUInt32(strLength);//写入消息的长度
_dataWriter.WriteString(message);//写入消息的内容
uint numBytesWritten = await _dataWriter.StoreAsync();
}
}
}
程序的运行效果如图19.2所示。
实例:实现蓝牙程序对设备的连接
下面给出蓝牙程序对设备连接的示例:查找蓝牙设备,并对找到的第一个蓝牙设备进行连接。
代码清单19-2:蓝牙程序对设备连接(源代码:第19章\Examples_19_2)
MainPage.xaml文件主要代码
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<Button x:Name="btFindBluetooth" Content="连接周围的蓝牙设备" Click="btFindBluetooth_Click"/>
</StackPanel>
</Grid>
MainPage.xaml.cs文件主要代码
// 查找蓝牙设备事件处理
private async void btFindBluetooth_Click(object sender, RoutedEventArgs e)
{
try
{
// 配置PeerFinder蓝牙服务的GUID去搜索设备
PeerFinder.AlternateIdentities["Bluetooth:SDP"] = "5bec6b8f-7eba-4452-bf59-1a510745e99d";
var peers = await PeerFinder.FindAllPeersAsync();
if (peers.Count == 0)
{
Debug.WriteLine("没发现蓝牙设备");
}
else
{
// 连接找到的第一个蓝牙设备
PeerInformation selectedPeer = peers[0];
StreamSocket socket = new StreamSocket();
await socket.ConnectAsync(selectedPeer.HostName, selectedPeer.ServiceName);
MessageBox.Show("连接上了HostName:" + selectedPeer.HostName + "ServiceName:" + selectedPeer.ServiceName);
}
}
catch (Exception ex)
{
if ((uint)ex.HResult == 0x8007048F)
{
MessageBox.Show("Bluetooth is turned off");
}
}
}
程序的运行效果如图19.3所示
window phone 8蓝牙详细开发案例说明
WP8蓝牙支持对等方应用连接,也支持其他蓝牙设备,下面我们看看如何连接到对等方应用和设备。
1)连接到对等方
[C#代码]
//已经搜索到的对等方列表
IReadOnlyList<PeerInformation> peers;
// 开始连接到对等应用
async void AppToApp()
{
// 开始查找对等项,如果有这句话,即可使自己能够被其他蓝牙设备搜索到
PeerFinder.Start();
peers = await PeerFinder.FindAllPeersAsync();
if (peers.Count == 0)
{
// 没有发现
}
else
{
// 选择第一个对等应用
PeerInformation selectedPeer = peers[0];
// 连接到第一个对等方应用
var streamSocket = await PeerFinder.ConnectAsync(selectedPeer);
}
}
2)连接到设备
PeerFinder.AlternateIdentities["Bluetooth:Paired"] = ""; 查找所有已配对的设备。这样连接找到的设备对应的PeerInformation.ServiceName将为空,所以我们不能通过PeerFinder.ConnectAsync(selectedPeer);的方式去连接,具体示例如下:
[C#代码]
//搜寻全部蓝牙设备并连接第一个
private async void AppToDevice()
{
// 搜索所有配对的设备
PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";
var pairedDevices = await PeerFinder.FindAllPeersAsync();
if (pairedDevices.Count == 0)
{
//没有发现设备
}
else
{
// 选择第一个连接的设备,此时selectedDevice.ServiceName为空
PeerInformation selectedDevice = pairedDevices[0];
// 主动创建一个StreamSocket
StreamSocket socket = new StreamSocket();
// 第二个参数是一个RFCOMM端口号,范围是1-30
await socket.ConnectAsync(selectedDevice.HostName, "1");
}
}
PeerFinder.AlternateIdentities["Bluetooth:SDP"] = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";查找使用服务发现协议 (SDP) 并通过既定 GUID 播发服务的设备
[C#代码]
//搜寻特定GUID的设备
private async void AppToDevice2()
{
PeerFinder.AlternateIdentities["Bluetooth:SDP"] = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
var pairedDevices = await PeerFinder.FindAllPeersAsync();
if (pairedDevices.Count == 0)
{
//没有发现设备
}
else
{
// 选择第一个连接的设备
PeerInformation selectedDevice = pairedDevices[0];
// 主动创建一个StreamSocket
StreamSocket socket = new StreamSocket();
// 这种情况下selectedDevice.ServiceName等于您指定的GUID
await socket.ConnectAsync(selectedDevice.HostName, selectedDevice.ServiceName);
}
}
3)侦听连接请求
[C#代码]
public Page1()
{
InitializeComponent();
//侦听连接请求需要先添加事件
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
//远程对等类异步连接时触发
PeerFinder.ConnectionRequested += PeerFinder_ConnectionRequested;
}
async void PeerFinder_ConnectionRequested(object sender, ConnectionRequestedEventArgs args)
{
// 获取到请求连接的对等方
var peer = args.PeerInformation;
// 回应连接
var streamSocket = await PeerFinder.ConnectAsync(peer);
}
4)发送消息
[C#代码]
/// <summary>
/// 在已连接的情况下发送消息
/// </summary>
/// <param name="socket"></param>
public async void Send(StreamSocket socket, string msg)
{
var _dataWriter = new DataWriter(socket.OutputStream);
//写入消息的长度
uint strLength = _dataWriter.MeasureString(msg);
_dataWriter.WriteUInt32(strLength);
//写入消息的内容
_dataWriter.WriteString(msg);
uint numBytesWritten = await _dataWriter.StoreAsync();
}
5)接收消息
[C#代码]
/// <summary>
/// 在已连接的情况下读取消息
/// </summary>
/// <param name="socket"></param>
public async Task<string> Read(StreamSocket socket)
{
var _dataReader = new DataReader(socket.InputStream);
// 读取消息长度
await _dataReader.LoadAsync(sizeof(uint));
uint msgLength = (uint)_dataReader.ReadUInt32();
// 读取消息的内容
await _dataReader.LoadAsync(msgLength);
return _dataReader.ReadString(msgLength);
}