基于蓝牙适配器的PC与Android端通讯

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/daihuimaozideren/article/details/80938366

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(着重感谢)

结语

第一次涉及蓝牙项目,耗时较长,且深入不够,若有疏漏,还望提出。

猜你喜欢

转载自blog.csdn.net/daihuimaozideren/article/details/80938366