1、要操作蓝牙,必须先获得本地的蓝牙适配器,在Xamarin里使用如下语句便可获得,与Java没多大区别。
BluetoothAdapter localAdapter = BluetoothAdapter.DefaultAdapter;
一般设备只有一个蓝牙模块,所以用DefaultAdapter就好,如果有多个的话,我暂时也没有找到解决方案,如果哪位有办法,请告诉我,感激不尽。
2、打开蓝牙设备。有人说可以调用一个打开蓝牙的Activity:
if (!bluetoothAdapter.IsEnabled) { Intent enableIntent = new Intent (BluetoothAdapter.ActionRequestEnable); StartActivityForResult (enableIntent, REQUEST_ENABLE_BT); }
这样会打开一个弹窗,要点击确定才能打开蓝牙。比较麻烦,我比较喜欢这样:
if (!localAdapter.IsEnabled) { localAdapter.Enable ();}
这样不会弹窗提示,而是静默打开蓝牙,代码也少,嗯。
但是需要注意,在执行localAdapter.Enable ()后,这个函数会立即返回,并不会等待蓝牙成功打开。所以在调用它之后是不能直接操作localAdapter的,需要等到蓝牙完全打开。至于如何获得蓝牙状态,在BluetoothAdapter中有个State成员,这是一个枚举变量,它标明了蓝牙的当前状态。
3、获得已配对的设备列表。在已获得的localAdapter中有一个成员,定义如下:
public ICollection<BluetoothDevice> BondedDevices
可见,它是一个集合。不过为了方便使用,我更喜欢如下方法:
List<BluetoothDevice> bondedDevices= new List<BluetoothDevice> (localAdapter.BondedDevices);
这样就可以获得一个已配对的设备列表。
4、类似于Socket通讯,蓝牙传输需要建立一个socket server。关于Socket不再赘述。代码如下:
BluetoothServerSocket serverSock = localAdapter.ListenUsingRfcommWithServiceRecord ("Bluetooth", Java.Util.UUID.FromString ("xxxx-xxxx-xxxx-xxxx-xxxxxxx"));BluetoothSocket sock = serverSock.Accept ();serverSock.Close();//服务器获得连接后腰及时关闭ServerSocket//启动新的线程,开始数据传输Thread t = new Thread(connected);t.Start(sock);
怎么样,是不是和Socket很相似?但需要注意的是ListenUsingRfcommWithServiceRecord()函数的参数。这个函数包含两个参数:第一个是字符串类型,标明这个ServerSocket连接的名称(但是我目前还没有发现它有什么用……);第二个参数是一个UUID,这个参数必须是明确指定的,因为蓝牙通讯双方必须持有相同的UUID才能够建立通讯。这个函数返回一个BluetoothServerSocket,接下来就可以用这个BluetoothServerSocket来进行Accept,等待客户端的连接了。
与Socket相同的是,Accept()函数会阻塞线程,千万不要放在UI线程里!!!
5、客户端连接。我会遍历已配对的设备尝试连接,然后代码如下:
foreach (BluetoothDevice d in bondedDevices) { BluetoothSocket sock = d.CreateRfcommSocketToServiceRecord (Java.Util.UUID.FromString ("xxxx-xxxx-xxxx-xxxx-xxxxxxx")); try{ sock.Connect();//连接服务器 //启动新的线程,开始传输数据 Thread t = new Thread(connected); t.Start(sock); break; }catch(Exception e){ sock.Dispose (); continue; }}
每个设备实例的CreateRfcommSocketToServiceRecord()函数会返回一个BluetoothSocket,当然这个函数的参数需要与服务器的UUID相同。得到返回的BluetoothSocket之后,就可以调用connect()函数尝试连接服务器了。需要注意的是:connect()函数会阻塞线程,并且由于在connect()函数执行时才会真正尝试和服务器建立连接,所以大部分的连接错误会在此处报错,要注意try-catch。在connect()函数报错后,为了下一次connect()函数能够顺利执行,请及时调用Dispose()销毁资源。一旦connect()成功,就会使服务端Accept()函数返回。
有没有发现,connect()函数和Accept()函数都会返回一个BluetoothSocket?
有没有发现,客户端连接成功后,与服务器获得连接后,启动的是同一个方法来传输数据?
因为无论是客户端还是服务器,都依靠一个BluetoothSocket进行数据传输,所以其数据收发过程是一样的,当然可以用同一个方法。这就是接下来的步骤。
6、数据收发。与java开发Android类似,数据收发都通过流来实现。BluetoothSocket有两个流成员,分别是BluetoothSocket.InputStream 和 BluetoothSocket.OutputStream。以我近乎于四级的英语水平来看:InputStream必定是接收数据的流,而OutputStream是发送数据的流。既然是流,那么读写就方便的多了:
暴力一点,直接
InputStream.Read()OutputStream.Write()
读写字节数组,但是想要获得有效的数据还得经过复杂的类型转换,才能把字节数组转换为需要的数据。
优雅一点,可以如下:
StreamReader sReader = new StreamReader (sock.InputStream);StreamWriter sWriter = new StreamWriter (sock.OutputStream);
这样可以通过StreamReader 和 StreamWriter 直接读写字符串。是不是方便的多?
需要注意的是,无论是直接调用Read()函数还是通过StreamReader读取流,都会阻塞线程,读取操作在没有获得数据的情况下会一直等待,直到获得数据并返回数据长度。
虽然在sock.InputStream中有ReadTimeout这么一个成员。我也曾以为
sock.InputStream.ReadTimeout = 1000;
这句代码会有用。但是直到我发现sock.InputStream.CanTimeout这个值永远都是false的时候,我发现我太天真了。
7、关闭连接。就跟随手关门、随手关灯一样,在不需要继续连接的时候请关闭连接,直接调用
sock.Close();
当然,数据收发双方,当有一方调用Close()函数后,双方对BluetoothSocket的调用都会报错,记得try-catch,否则程序会直接关闭(别问我怎么知道的)。
嗯,大约就这样。好像整个流程里少了扫描设备这个过程,嗯……本人才疏学浅,尚未参透扫描设备的方法,待他日我大彻大悟再来哔哔。或者哪位大哥愿意指教,小弟不胜感激。
再补充一句,小弟没有Android开发经验,以上内容纯属自己摸索,请大家不吝赐教,谢谢!
联系客服