Demo
首先,直接给Demo,对于只想使用的朋友,直接下载使用即可。Demo其实也是从网上爬来的,之后做了各种调试和修改。
原有Demo代码下载,可见地址。
修改后Demo效果如下。效果不太清晰,见谅。
(1)PC端
(2)Android端
细节实现
Android端
android端做为客户端要与PC通讯,需要完成以下几步。添加蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
注册广播接收器
个人认为该步骤可选。但最好存在。
原因在于,虽然蓝牙适配器可以获取周围蓝牙设备的列表,但对于周围蓝牙设备的扫描比较耗时。
返回蓝牙设备列表时,可能仍处于搜索过程中。
当扫描完成时,广播接收器将受到action为ACTION_DISCOVERY_FINISHED的广播。此时,获取设备列表,将比较全面。
建议接收器监听,以下三个action。
IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(BluetoothDevice.ACTION_FOUND);//发现设备intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//扫描完毕intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//扫描结束registerReceiver(broadcastReceiver, intentFilter);
开启蓝牙
if (!bluetoothAdapter.isEnabled()) {bluetoothAdapter.enable();}
开启适配器后,android将开始遍历周围可以被访问的蓝牙设备。该动作将触发广播。
当然,也可以通过触发bluetoothAdapter.startDiscovery()重新扫描。
选定目标蓝牙设备
由适配器获取设备列表,并根据PC的bluetooth MAC地址,选定目标蓝牙设备。SERVICE_ADDRESS为PC端蓝牙mac地址,格式为XX:XX:XX:XX:XX:XX
Set<BluetoothDevice> mySet = bluetoothAdapter.getBondedDevices();for (BluetoothDevice device : mySet) {if (device.getAddress().equalsIgnoreCase(SERVICE_ADDRESS) ) {service = device;break;}}
创建RfcommSocket并建立连接
private static final String serverUUID = "00001101-0000-1000-8000-00805F9B34FB"private BluetoothSocket bluetoothSocket;bluetoothSocket = service.createRfcommSocketToServiceRecord(UUID.fromString(serverUUID));bluetoothSocket.connect();
这里需要说明的有两点。
1.连接操作比较耗时,视具体设备不同。所以,需要将连接操作放到子线程中完成。
2.在Android端,需要使用UUID,完成与其他蓝牙设备的连接。关于UUID,之后小结会提到。
收发数据
收发数据也较为耗时,需要放在子线程实现。
OutputStream outputStream;try {outputStream = bluetoothSocket.getOutputStream();outputStream.write("A message from android device".getBytes());showMessage("Successfully send message");} catch (IOException e) {showMessage(e.getMessage()+", during output");}InputStream inputStream;try {inputStream = bluetoothSocket.getInputStream();byte[] buffer = new byte[200];inputStream.read(buffer);showMessage("Concurrently receive message : " + new String(buffer));} catch (IOException e) {showMessage(e.getMessage()+", during get input");}
关闭RfcommSocket
bluetoothSocket.close();
PC端
设置PC蓝牙设备可见
LocalDevice.getLocalDevice().setDiscoverable(DiscoveryAgent.GIAC);
创建连接流监听器
private StreamConnectionNotifier streamConnectionNotifier;
streamConnectionNotifier = (StreamConnectionNotifier) Connector.open("btspp://localhost:" + SERVER_UUID.toString());
注意,此处使用的UUID,必须与android端的UUID一致。
开启监听
StreamConnection streamConnection = null;
streamConnection = streamConnectionNotifier.acceptAndOpen();
acceptAndOpen()方法调用后,将进入等待。
获取输入流和输出流
while (isListening) {if ((inputStream.available()) <= 0) {Thread.sleep(1000);}System.out.println("message is comming");outputStream.write("hello android BT".getBytes());inputStream.read(buffer);String message = new String(buffer);System.out.println("Receive message : " + message);if (message.contains("EXIT_APP")) {System.out.println("Listener closed");isListening = false;}
}
关闭连接
inputStream.close();
outputStream.close();
streamConnection.close();
问题
BUG
java.io.IOException: read failed, socket might closed or timeout, read ret: -1
在连接时,常会遇到该BUG。网上很多方法说,可以通过修改UUID的方式,来FIX该BUG。
但翻看createRfcommSocketToServiceRecord方法的注释,发现该UUID不能随便修改。
* <p>Hint: If you are connecting to a Bluetooth serial board then try* using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.* However if you are connecting to an Android peer then please generate* your own unique UUID.
因此,若是采用串口通信,必须使用“00001101-0000-1000-8000-00805F9B34FB”。
造成连接失败的另一原因,可能是channel ID的问题。
在创建Socket时,createRfcommSocketToServiceRecord使用UUID作为唯一传参,而默认channel为-1。
网上建议,通过反射使用BluetoothDevice的隐藏public createRfcommSocket方法,利用传参指定的channel值,创建Socket。
channel的取值范围为1至30.
Demo中给出了工具方法。
public BluetoothSocket cretateBluetoothSocketbyChannel(BluetoothDevice Device,int channel,boolean autoForward){BluetoothSocket socket=null;try {showMessage("Trying fallback on channel "+channel);socket =(BluetoothSocket) Device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(Device,channel);socket.connect();Log.d(TAG,"createRfcommSocket on channel "+channel);showMessage("Successfully connect");} catch (Exception e) {showMessage(e.getMessage());if(channel<30){if(autoForward){socket=cretateBluetoothSocketbyChannel(Device,channel+1,autoForward);}else {showMessage("Connect Failed");}}}return socket;}
另外,就该问题,吐槽一下适配器。适配器的性能也是参差不齐。此前摁着绿联的蓝牙适配器,试了3天。同样的代码,问题百出。后来转用奥视通(ost108),几分钟便过了。不管是添加设备时的认证过程的人性化设计,还是设备服务驱动的安装速度,天壤之别~
驱动
Demo在实现时,遇到找不到设备的情况。个人感觉是由于适配器所提供的驱动问题造成的。在卸载后,使用通用驱动可以解决该问题。
Bluecove版本
在64位OS下开发,需要使用Bluecove 64bit版本,本Demo使用为64bit。下载地址
参考文献
在开发过程中,以下文章给予了很多帮助,一并列下。
https://blog.csdn.net/tingfengzheshuo/article/details/45292201
http://royal2xiaose.iteye.com/blog/1420138
https://blog.csdn.net/old_me_mory/article/details/18962701
https://blog.csdn.net/peceoqicka/article/details/51979469(着重感谢)
结语
第一次涉及蓝牙项目,耗时较长,且深入不够,若有疏漏,还望提出。