蓝牙数据传输问题
对于蓝牙来说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。
实际开发中需要注意的地方.
需要注意的
- 因为涉及涉及到I/O编程,所以需要注意两端的编码'utf-8'要一致.
- 客户端与服务端的UUID也要相同.
- 蓝牙属于底层数据传输,所以实际开发更多发送的是16进制数据.
- 因为涉及到了I/O编程,所以对线程控制这块(同步,锁机制)需要注意使用.
- 对于一些大容量字节数组的发送需要注意的地方.
- 为了用户更好的体检避免流量的过度浪费,使用阻塞式的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);
}