Android BLE BluetoothGatt蓝牙通信封装成InputStream和OutputStream

此类封装了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<byte[]> rxQueue = new LinkedBlockingQueue<byte[]>(); // 接收队列
    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<byte[]> txQueue = new LinkedList<byte[]>();
            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<byte[]> txQueue = new LinkedList<byte[]>();
            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<BluetoothGattService> list = gatt.getServices();
        for (BluetoothGattService service : list) {
            List<BluetoothGattCharacteristic> 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<byte[]> 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();
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android BLE蓝牙低功耗)是一种用于与低功耗设备通信的无线技术。在开发和调试过程中,我们可以使用一些调试工具来帮助我们监控和分析BLE连接和通信过程中的数据。下面是关于Android BLE蓝牙调试工具代码的一些说明: 首先,我们可以使用Android提供的Log工具来打印调试信息。通过在代码中插入Log语句,我们可以在Logcat工具中查看和分析输出的日志信息,以了解BLE连接和通信的过程中发生的事件和数据。 其次,Android提供了一个名为Bluetooth HCI日志记录器(bluetooth_hci.log)的工具,用于记录与BLE通信相关的底层蓝牙协议数据。我们可以通过在设备上运行以下命令来启用该日志记录器: adb shell setprop persist.bluetooth.bluetooth_hci_log true 启用后,BLE通信过程中的底层协议数据将被记录到bluetooth_hci.log文件中。我们可以使用ADB工具来获取该文件,然后使用Wireshark等工具进行分析。 另外,我们还可以使用第三方蓝牙调试工具库,例如nRF Connect或BLE Scanner,来分析和调试BLE连接和通信过程。这些工具通常提供了扫描设备、连接设备、发现服务和特征、读写特征值等功能,可以帮助我们与BLE设备进行交互,并监控和分析通信过程中的数据。 在代码中使用这些蓝牙调试工具时,我们可以根据需要调用相应的API,例如启动扫描、连接设备、发现服务和特征、读写特征值等,然后根据返回的数据和事件进行进一步处理和分析。 总之,Android BLE蓝牙调试工具代码包括使用Log工具打印日志信息、启用Bluetooth HCI日志记录器、使用第三方蓝牙调试工具库等,可以帮助我们监控和分析BLE连接和通信过程中的数据,以便进行调试和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值