蓝牙通信之保证数据的完整性

由来

之前写过一篇Android蓝牙通信的文章,介绍了Android蓝牙的使用和一款蓝牙聊天app的原理,可以点这里看这篇文章。

本篇文章是对上篇文章的完善和补充。

原有问题

上篇文章在分析蓝牙聊天app时提到过“管理连接线程”,在该线程中无限循环读取 InputStream,如果读取异常,说明蓝牙中断。
而读取的方法也很简单,直接读取数据到字节数组中:

byte[] buffer = new byte[1024];
mInStream.read(buffer);

之前以为给buffer分配的空间大点,就能保证一次接收到的数据是完整的。
然,非也!
这样做还会抢占紧张的内存,严重时甚至导致OOM!

分析问题

read(byte b[])方法调取了另一个重载方法read(b, 0, b.length),该方法的源码如下:

public int read(byte b[], int off, int len) throws IOException {
    if (b == null) {
        throw new NullPointerException();
    } else if (off < 0 || len < 0 || len > b.length - off) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return 0;
    }

    int c = read();
    if (c == -1) {
        return -1;
    }
    b[off] = (byte)c;

    int i = 1;
    try {
        for (; i < len ; i++) {
            c = read();
            if (c == -1) {
                break;
            }
            b[off + i] = (byte)c;
        }
    } catch (IOException ee) {
    }
    return i;
}

再提取源码注释中的几个关键信息:
1. This method blocks until input data is available, end of file is detected, or an exception is thrown.
2. An attempt is made to read as many as len bytes, but a smaller number may be read.
The number of bytes actually read is returned as an integer
3. In every case, elements b[0] through b[off] and elements b[off+len] through b[b.length-1] are unaffected.

结合源码和注释,可以得出:
1. 该方法会阻塞直到有数据读入或到文件末尾或抛出异常;
2. 读取的字节数会尽可能和参数len一样大,但可能比它小,实际读取的字节数在返回值中。
3. 读取后,b[0]至b[off]和b[off+len]至b[b.length-1]的值没有变化,改变的只是b[off]至b[off+len]的值。

因此,每次读取的字节数是不确定的,当数据量大时,读取一次很可能就是不完整的数据。
而经过测试,蓝牙发送方发送一次数据,接收方哪怕通过多次读取把数据完整接收后,read()方法也不会返回-1,而是阻塞着等待下一段数据的到来。
于是,需要另外寻找”数据读取完毕”的临界点。

解决问题

一段数据哪怕分多次读取,相邻两次读取的间隔时间也是很快的,这和阻塞有很明显的区别。
于是可以利用倒计时来判断一段数据是否已经读取完整,具体逻辑如下:
1. 读取数据后拼接数据,开始一个短暂的倒计时。
2. 倒计时结束表示一段数据已经读取完整,开始使用数据。
3. 如果倒计时结束前又读取到新数据,则取消倒计时,重复步骤1。

核心代码如下:

private StringBuilder jsonBuilder = new StringBuilder();
private BlueWaitingTimer timer = new BlueWaitingTimer();

private class BlueWaitingTimer extends CountDownTimer {

    public BlueWaitingTimer(long millisInFuture, long countDownInterval) {
        super(millisInFuture, countDownInterval);
    }

    @Override
    public void onTick(long millisUntilFinished) {

    }

    @Override
    public void onFinish() {
        //一段数据接收完毕,开始使用数据,使用后清空StringBuilder
        jsonBuilder.delete(0, jsonBuilder.length());
    }
}

private class ConnectedThread extends Thread {

    @Override 
    public void run() {
        byte[] buffer = new byte[1024];
        int count;
        while (true) {
            try {
                count = mInStream.read(buffer);
                timer.cancel();
                jsonBuilder.append(new String(buffer, 0, count, "UTF-8"));
                timer.start();
            } catch (IOException e) {
                 break;
            }
        }
    }
}

注意:此处的BlueWaitingTimer不可以在线程ConnectedThread中实例化,否则倒计时结束时将被read方法阻塞得不到执行。

猜你喜欢

转载自blog.csdn.net/recordGrowth/article/details/78894656