我正在后台线程上读取蓝牙数据流,并将该数据传递给UiThread,以便通过消息处理程序进行处理和显示。关于这个过程的更详细的描述可以在这里找到。
目前,我正在将InputStream读取到byte[]
,并使用Handler.geourenMessage()来获取一个带有字节数组作为对象参数的消息。
我遇到了数据包相互覆盖的问题,因为数组没有跨线程锁定,并且在UiThread处理它之前在后台被覆盖。显而易见的解决方案是将数组复制到一个新数组并传递该新对象。
问题是,这在安卓系统上非常昂贵,我将获得相当连续的数据流。有没有更好的方法将这些数据传递到主UiThread?也许是通过同步字节数组,还是通过一种更有效的内存复制数组的方式?
在Android中执行此操作的典型方式是拥有一个已分配对象池。制作此类:
class DataBuffer {
DataBuffer next;
byte[] data;
}
你可以这样管理它们:
final Object mLock = new Object();
DataBuffer mPool;
DataBuffer obtain() {
synchronized (mLock) {
if (mPool != null) {
DataBuffer res = mPool;
mPool = res.next;
return res;
}
return new DataBuffer();
}
}
void recycle(DataBuffer buffer) {
synchronized (mLock) {
buffer.next = mPool;
mPool = buffer;
}
}
听起来您想要的是一个字节数组池,您可以在完成后签出使用并签入。一个实现可能看起来像
public class BytePool {
private class PoolObject {
public byte[] buffer = byte[1024];
public boolean available = false;
}
ArrayList<PoolObject> pool = new ArrayList<PoolObject>();
public synchronized byte[] checkOut() {
boolean found = false;
for (PoolObject obj : pool) {
if (obj.available) {
obj.available = false;
return obj.buffer;
}
}
PoolObject newObj = new PoolObject();
pool.add(newObj);
return newObj.buffer;
}
public synchronized void checkIn(byte[] finished) {
for (PoolObject obj : pool) {
if (obj.buffer == finished) {
obj.available = true;
}
}
}
}
免责声明:我还没有真正测试甚至编译过这段代码,它只是为了传达基本的想法。
这样一来,您就有望在任何给定的时间分配最小数量的byte[]对象。理想情况下,如果出于某种原因,它扩展了一堆,而现在所需的池大小更小,那么您还希望添加一些代码来减少池大小。
就我个人而言,在明确是否需要之前,我认为您可能对微观优化过于担忧。(或者你已经对你的应用程序进行了分析,并且知道你需要它。)
更新:事实上,Hackbod的解决方案比我建议的更高效,尽管它仍然可能因为池中有太多对象而受到影响。
数据进入和消耗的速度有多快?
为什么不分配一个第二个数组,一个用于读取,另一个用于写入,根据需要进行切换。
当流完成对数组1的写入时,释放其互斥对象,等待数组2变为可用。然后,您可以通过处理程序将数组1传递给UI线程,UI线程将获得该数组的锁。通过这样切换阵列,您可以确保数据不会丢失,因为对每个阵列的访问都是通过互斥控制的。
然而,如果您的数据输入速度快于读取速度,则可能会出现问题。(蓝牙数据蒸汽能承受阻塞和等待吗?)
不了解android,但通常会将此类数据读取到动态分配的缓冲区中,在消息中发布其引用,然后立即分配一个新的缓冲区,为下一批数据做好准备。这消除了复制,并确保两个线程始终对不同的数据进行操作。
Rgds,Martin