通过具有最小分配的处理程序消息传递数组的副本



我正在后台线程上读取蓝牙数据流,并将该数据传递给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

最新更新