首页 > 代码库 > 蓝牙之数据传输问题

蓝牙之数据传输问题

蓝牙数据传输问题

对于蓝牙来说google已经封装好了很多api所以使用起来并不会很难,但是实际开发中蓝牙开发最头疼的问题不是如何去调用api,而是数据的交互方面,如长连接,数据续传,硬件接受速率等问题.

打开蓝牙有几种方式?

首先我们先了解下几种常用的打开方式.

  • 第一种方法相对简单,直接调用系统对话框启动蓝牙:
    在AndroidManifest文件中添加需要的权限,高版本也不需要动态授权:
<uses-permission android:name="android.permission.BLUETOOTH" />
//处理回调对话框
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);
  • 第二种方法,静默开启,不会有方法一的对话框:
    在AndroidManifest文件中添加需要的权限:
    • 在AndroidManifest中配置需要的权限.
    • 对于6.0的运行时权限进行适配,在java中动态授权.
    • 最后直接调api开启
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.enable(); //开启
//mBluetoothAdapter.disable(); //关闭

如何搜索蓝牙设备?

搜索分为:主动搜索和被动搜索。

  • 主动搜索
    • 创建BluetoothAdapter对象
    • 配对的蓝牙设备列表
    • 定义发送接收广播

蓝牙的UUID是什么?有什么用?

UUID(Universally Unique Identifier) 统一表示定义.
蓝牙是通过串口发送AT命令,蓝牙默认是在数据模式的,要配置为AT命令模式,对其进行设置,不过UUID在出厂前是设置过的.
对于蓝牙设备,每个服务都有一个与它对应的UUID(唯一的).
如:
信息同步服务:00001104-0000-1000-8000-00805F9B34FB
文件传输服务:00001106-0000-1000-8000-00805F9B34FB

如何使用蓝牙进行数据传输?

蓝牙模块通信最重要的地方就是数据的发送和接收,其传输数据与Socket类似。

  • 在网络中使用Socket和ServerSocket控制客户端和服务端的数据读写。
  • 而蓝牙通讯也由客户端和服务端Socket来完成。蓝牙客户端Socket是BluetoothSocket,蓝牙服务端Socket是BluetoothServerSocket。这两个类都在android.bluetooth包中。
  • 无论是BluetoothSocket,还是BluetoothServerSocket,都需要一个UUID(全局唯一标识符,Universally Unique Identifier),UUID相当于Socket的端口,而蓝牙地址相当于Socket的IP。

实际开发中需要注意的地方.

需要注意的
1. 因为涉及涉及到I/O编程,所以需要注意两端的编码’utf-8’要一致.
2. 客户端与服务端的UUID也要相同.
3. 蓝牙属于底层数据传输,所以实际开发更多发送的是16进制数据.
4. 因为涉及到了I/O编程,所以对线程控制这块(同步,锁机制)需要注意使用.
5. 对于一些大容量字节数组的发送需要注意的地方.
6. 为了用户更好的体检避免流量的过度浪费,使用阻塞式的InputStream读取.

1. 因为涉及涉及到I/O编程,所以需要注意两端的编码’utf-8’要一致.

os.write("datas....".getBytes("utf-8"));

2. 客户端与服务端的UUID也要相同.

//客户端Socket
device.createRfcommSocketToServiceRecord(MY_UUID);
//服务端Socket
mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);

3. 蓝牙属于底层数据传输,所以实际开发更多发送的是16进制数据.

public static String bytesToHexString(byte[] bytes) {
        String result = "";
        for (int i = 0; i < bytes.length; i++) {
            String hexString = Integer.toHexString(bytes[i] & 0xFF);
            if (hexString.length() == 1) {
                hexString = ‘0‘ + hexString;
            }
            result += hexString.toUpperCase();
        }
        return result;
    }

5. 对于一些大容量字节数组的发送需要注意的地方.

我们需要发送64个字节的数组,如果一次性发送过去,单片机那里可能无法及时处理以致没有任何回应,因为单片机那里是设置了数据接收的延时时间。要想畅通的与蓝牙模块通信,考虑这个时间差非常重要。调整字节的发送速率,就成为非常关键的一步。值得注意的是,数据的发送是非常快的,就是因为这样才会导致单片机那里无法及时处理,所以,每次发送后的延时是非常重要的。我们单片机那里的延时是10毫秒,所以我们选择发送完每个字节后就延时10毫秒再发下个字节。

 for (byte b : bytes) {
     out.write(b);
     Thread.sleep(10);
 }

6.为了用户更好的体检避免流量的过度浪费,使用阻塞式的InputStream读取.

在使用InputStream的时候,必须注意,InputStream的读取是阻塞的。这点在一般的情况下是不会影响到我们的程序,但是记住这个情况对于代码的设计是非常重要的,尤其是在考虑用户体验的时候。
无参数的read()是每次只从流中读取一个字节,这种做法效率非常低,但是简单,像是读取整数值这种情况,使用read()就非常好,但如果是16进制字符串呢?使用InputStream.read(byte[] b)或者InputStream.read(byte[] b,int off,int len)方法,这样一次就能读取多个字节。
如果是读取多个字节,我们常常使用InputStream.available()方法来获取数据流中可读字节的个数。读取本地数据的时候,该方法发挥得非常好,但如果是读取非本地数据,就可能出现字节遗漏的问题,像是要读取100个字节,可能就是90个,甚至是0个。
出现0个的情况就是单片机那边没有响应或者字节还没发送过来,这时我们就需要一个循环来保证我们能够拿到数据:

  int count = 0;
  while (count == 0) {
   count = in.available();
  }
  byte[] bytes = new byte[count];
  in.read(bytes);
      但像是上面的90个字节的情况就是字节遗漏。对于这种情况,解决方法也很简单:
  byte[] bytes = new byte[count];
  int readCount = 0; // 已经成功读取的字节的个数
  while (readCount < count) {
   readCount += in.read(bytes, readCount, count - readCount);
  }

部分内容参考链接

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    蓝牙之数据传输问题