此类封装了BLE蓝牙的数据收发操作,使用起来很方便。
构造函数DeviceConnection(BluetoothDevice device, Context context)的参数分别是要连接的蓝牙设备以及当前Activity对象。
调用getInputStream获取输入流,用于接收数据。调用getOutputStream获取输出流,用于发送数据。
waitForConnection函数用于等待蓝牙连接建立,是可选的操作。不需要等待连接建立好,就可以直接调用收发数据的函数,若连接建立失败则会抛出异常。
一定要把对象写在try的括号里面,抛出异常时才会自动关闭连接,整个语句块里面也无需再写close()。
try (
DeviceConnection conn = new DeviceConnection(device, context);
InputStream in = conn.getInputStream();
OutputStream out = conn.getOutputStream();
) {
} catch (IOException e) {
e.printStackTrace();
}
扩展阅读:
Java DataOutputStream writeInt和writeShort如何输出小端序字节序(Little Endian)
package com.oct1158.uartdfu;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import org.jetbrains.annotations.NotNull;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
// 请注意Closeable是继承AutoCloseable的, 不要搞反了
// Closeable的close抛出的是IOException异常, 而AutoCloseable的close抛出的是Exception异常
// 只要使用try (obj) {..} catch语句块, obj离开try语句块后就会自动调用close()关闭
public class DeviceConnection extends BluetoothGattCallback implements Closeable {
public static final int SERVICE_UNKNOWN = 0;
public static final int SERVICE_NOT_FOUND = 1;
public static final int SERVICE_FOUND = 2;
private boolean closed;
private int serviceState;
private int timeout = -1;
private BluetoothGatt gatt;
private BluetoothGattCharacteristic characteristic;
private LinkedBlockingQueue rxQueue = new LinkedBlockingQueue(); // 接收队列
private Semaphore txSem = new Semaphore(0); // 是否可以发送数据
private InputStream inputStream = new InputStream() {
// 收到的所有数据段按顺序组成一个队列
// 读取数据时, 从队列中取数据段
// 多余部分存放到segment和offset变量中, 供下次读取使用
private boolean closed;
private byte[] segment;
private int offset;
// read()函数只有当没有收到任何数据时, 才会抛出InterruptedIOException异常
// 因此, 不带参数的read()函数没有收到数据, 会抛出InterruptedIOException异常
// 带参数的read()函数没有收到数据, 也会抛出InterruptedIOException异常
// 带参数的read()函数如果收到了数据, 但没有接收完所有字节, 就不会抛出InterruptedIOException异常, 而是将已收到的字节数作为返回值返回
//
// FileInputStream不是阻塞性质的流, 遇到文件结尾时所有read()函数(无论是否带参数)都会返回-1, 不会阻塞, 这种流可以用BufferedReader.readLine一行一行地读取
// 但Socket的getInputStream流, 以及这里的inputStream流, 没有文件结尾的说法, 没有数据时会阻塞, 超时且没读到数据会抛出InterruptedIOException异常
// 这种阻塞性质的流是不能用BufferedReader.readLine来读取的
@Override
public synchronized int read() throws IOException {
if (closed) {
throw new IOException("Rx closed");
}
if (segment == null) {
try {
if (timeout == -1) {
segment = rxQueue.take();
} else {
segment = rxQueue.poll(timeout, TimeUnit.MILLISECONDS);
if (segment == null) {
throw new InterruptedIOException("Rx timeout"); // 超时
}
}
if (segment.length == 0) {
throw new InterruptedIOException("Rx closed");
}
} catch (InterruptedException e) {
throw new InterruptedIOException("Rx interrupted");
}
}
byte data = segment[offset];
offset++;
if (offset == segment.length) {
segment = null;
offset = 0;
}
// data是byte型变量, 当data=0xff时, return data会被转换成int类型
// 由于最高位符号位为1, 所以转换成int就会变成0xffffffff=-1(EOF)
// 如果不带参数的read()返回了-1, 那么带参数的read()就会认为文件结束了, 会提前返回
// 为了解决这个问题, 这里必须要&0xff, 这样当data=0xff时read()返回的就是0xff=255, 而不是0xffffffff=-1(EOF)
return data & 0xff;
}
@Override
public void close() throws IOException {
closed = true;
}
};
private OutputStream outputStream = new OutputStream() {
// 先将要发送的数据按20字节拆分成多段, 组成一个队列
// 然后开始发送, 阻塞等待发送完毕后, 函数返回
private boolean closed;
// 可以用PrintWriter.println输出字符行
// 但是请注意PrintWriter(OutputStream out)构造函数创建的对象是带缓存的, println()函数返回后数据不一定已交给OutputStream流输出, 必须要调用flush()或close()才会真正输出
// 最好选择PrintWriter(OutputStream out, boolean autoFlush)这个构造函数, 这样的话调用println()会自动flush()
//
// 还有一点要注意, println()函数不会抛出任何异常, 即使OutputStream.write()出错抛出了异常, println()也会吸收掉, 然后函数正常返回
// 直接调用write()函数发送数据, 如果蓝牙连接断开了, 会抛出异常, 程序可以捕获到, 但如果用println()的话就检测不到这个异常了
@Override
public void write(int b) throws IOException {
if (closed) {
throw new IOException("Tx closed");
}
Queue txQueue = new LinkedList();
byte[] segment = new byte[1];
segment[0] = (byte)b;
txQueue.offer(segment);
processTxQueue(txQueue);
}
// 所有的write()函数都会抛出InterruptedIOException异常
// 通常情况下必须要重写write(b, off, len)函数, 否则bytesTransferred的值不正确
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException("Tx closed");
}
Queue txQueue = new LinkedList();
while (len != 0) {
int curr = 20;
if (curr > len) {
curr = len;
}
byte[] segment = Arrays.copyOfRange(b, off, off + len);
txQueue.offer(segment);
len -= curr;
off += curr;
}
processTxQueue(txQueue);
}
@Override
public void close() throws IOException {
closed = true;
}
};
public DeviceConnection(@NotNull BluetoothDevice device, @NotNull Context context) {
gatt = device.connectGatt(context, false, this);
}
@Override
public void close() throws IOException {
if (closed) {
return;
}
closed = true;
if (inputStream != null) {
inputStream.close();
inputStream = null;
}
if (outputStream != null) {
outputStream.close();
outputStream = null;
}
if (gatt != null) {
gatt.disconnect();
gatt = null;
}
characteristic = null;
txSem.release();
try {
byte[] data = new byte[0];
rxQueue.put(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public InputStream getInputStream() {
return inputStream;
}
public OutputStream getOutputStream() {
return outputStream;
}
/* 判断蓝牙设备是否支持蓝牙串口服务 */
public int getServiceState() {
return serviceState;
}
/* 获取发送和接收的超时时间 */
public int getTimeout() {
return timeout;
}
public boolean isConnected() {
return characteristic != null && !closed;
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, @NotNull BluetoothGattCharacteristic characteristic) {
/* 接收到了串口数据 */
byte[] data = characteristic.getValue();
try {
rxQueue.put(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.onCharacteristicChanged(gatt, characteristic);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
/* 发送串口数据成功 */
txSem.release(); // 允许发送新数据段
}
super.onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (!closed) {
switch (newState) {
case BluetoothGatt.STATE_CONNECTED:
/* 连接成功 */
gatt.discoverServices(); // 开始查询蓝牙服务
break;
case BluetoothGatt.STATE_DISCONNECTED:
/* 连接断开 */
try {
close();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
super.onConnectionStateChange(gatt, status, newState);
}
@Override
public void onServicesDiscovered(@NotNull BluetoothGatt gatt, int status) {
List list = gatt.getServices();
for (BluetoothGattService service : list) {
List characteristics = service.getCharacteristics();
for (BluetoothGattCharacteristic characteristic : characteristics) {
String uuid = characteristic.getUuid().toString();
if (uuid.equals("0000ffe1-0000-1000-8000-00805f9b34fb")) {
// 找到蓝牙串口服务
serviceState = SERVICE_FOUND;
this.characteristic = characteristic;
if (closed) { // 多线程环境下, 应该先赋值, 后判断是否closed
this.characteristic = null;
break;
}
gatt.setCharacteristicNotification(characteristic, true); // 打开数据接收通知
txSem.release(); // 允许发送数据
break;
}
}
if (characteristic != null) {
break;
}
}
if (characteristic == null) {
// 未找到蓝牙服务
try {
serviceState = SERVICE_NOT_FOUND;
close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onServicesDiscovered(gatt, status);
}
/* 发送txQueue队列中的所有数据段, 发送完毕时函数才返回 */
private void processTxQueue(@NotNull Queue txQueue) throws IOException {
int count = 0;
while (!txQueue.isEmpty()) {
byte[] data = txQueue.poll(); // 从队列中取出要发送的数据段
try {
// 等待发送通道可用
if (timeout == -1) {
txSem.acquire();
} else {
boolean success = txSem.tryAcquire(timeout, TimeUnit.MILLISECONDS);
if (!success) {
// 超时
InterruptedIOException exception = new InterruptedIOException("Tx timeout");
exception.bytesTransferred = count;
throw exception;
}
}
} catch (InterruptedException e) {
InterruptedIOException exception = new InterruptedIOException("Tx interrupted");
exception.bytesTransferred = count;
throw exception;
}
boolean sent = false;
synchronized (this) {
if (characteristic != null) {
characteristic.setValue(data);
sent = gatt.writeCharacteristic(characteristic);
}
}
if (sent) {
count += data.length;
} else {
InterruptedIOException exception = new InterruptedIOException("Tx error");
exception.bytesTransferred = count;
throw exception;
}
}
}
/* 设置发送和接收的超时时间 (毫秒) */
public void setTimeout(int timeout) {
if (timeout < 0) {
timeout = -1; // 不超时, 一直阻塞
}
this.timeout = timeout;
}
/* 等待连接建立 */
// 这一步可以省略, 可以不等待连接建立直接write()或者read(), 若连接建立失败则会抛出异常
public boolean waitForConnection() {
return waitForConnection(-1);
}
public boolean waitForConnection(int timeout) {
if (closed) {
return false;
} else if (isConnected()) {
return true;
}
boolean acquired = false;
try {
if (timeout < 0) { // 小于0表示不超时
txSem.acquire();
acquired = true;
} else {
acquired = txSem.tryAcquire(timeout, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (acquired) {
txSem.release();
}
return isConnected();
}
}