【问题标题】:How to implement an endless CursorAdapter?如何实现一个无穷无尽的 CursorAdapter?
【发布时间】:2022-03-17 22:06:16
【问题描述】:

在我们公司,我们正在开发一款显示时间线的应用。我们愿意让用户(几乎)无限期地滚动它。

现在有两个事实需要考虑:

在当前实现中,我们默认加载 40 个项目,然后当用户滚动超过某个阈值时,我们通过将限制增加到 40+20 个项目来重复查询,依此类推。

但是,这种方法似乎相当弱,因为它与前面所述的两个原则相冲突:查询最终会变得相当大,并且在某些时候游标可能会达到 1MB 的内存限制(我们加载了很多字符串)。

现在我们正在考虑利用MergeCursor 并像这样继续:

  1. 第一次加载包含 40 个项目的游标
  2. 当用户滚动超过某个级别时,我们会加载另一个包含接下来 40 个项目的游标,并在游标适配器中设置一个 MergeCursor,它将新游标连接到前一个游标。
  3. 继续使用此方法直到最多 X 步(取决于测试)以避免遇到一些 OOM 异常。最后,时间线光标将是 X 光标的串联。

您如何看待这种方法?任何弱点(除了开销,应该很小)?

如果您能指出/描述更好的解决方案吗?

提前致谢

【问题讨论】:

  • 只是想知道:如果您将光标数据限制为仅显示所需的数据,是否仍然存在遇到所描述的可能问题的实际风险?我已经使用 CursorAdapter 处理了数千个项目,但并没有真正注意到主要的性能影响(尽管快速滚动和部分索引变得更具挑战性)。然而,一个有趣的问题!我想任何保留有限数量“页面”的解决方案都可以解决这个问题。还有什么限制吗? IE。前面提到的快速滚动和/或部分索引?
  • 然后使用 AbstractWindowedCursor
  • 感谢 cmets,我进行了更多调查,可能找到了解决我疑惑的方法。如果我错了,请告诉我。

标签: android android-listview android-cursor android-cursoradapter


【解决方案1】:

在 cmets 中,pskink 建议使用AbstractWindowedCursor

我对这门课不熟悉,稍微研究了一下。事实证明,SQLiteCursor 已经对其进行了扩展。文档说明了这一点:

光标拥有它使用的光标窗口。当光标关闭时,它的窗口也关闭。同样,当光标使用的窗口改变时,它的旧窗口将关闭。这种严格所有权的策略确保光标窗口不会泄露。

这意味着在任何给定时刻,从数据库中查询的数据中只有一小部分实际上保存在内存中。这是SQLiteCursor中代码的有趣部分:

@Override
public boolean onMove(int oldPosition, int newPosition) {
    // Make sure the row at newPosition is present in the window
    if (mWindow == null || newPosition < mWindow.getStartPosition() ||
            newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
        fillWindow(newPosition);
    }

    return true;
}

@Override
public int getCount() {
    if (mCount == NO_COUNT) {
        fillWindow(0);
    }
    return mCount;
}

private void fillWindow(int requiredPos) {
    clearOrCreateWindow(getDatabase().getPath());

    try {
        if (mCount == NO_COUNT) {
            int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
            mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
            mCursorWindowCapacity = mWindow.getNumRows();
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
            }
        } else {
            int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
                    mCursorWindowCapacity);
            mQuery.fillWindow(mWindow, startPos, requiredPos, false);
        }
    } catch (RuntimeException ex) {
        // Close the cursor window if the query failed and therefore will
        // not produce any results.  This helps to avoid accidentally leaking
        // the cursor window if the client does not correctly handle exceptions
        // and fails to close the cursor.
        closeWindow();
        throw ex;
    }
}

这意味着两件事:

  1. 加载整个数据集应该是安全的,因为它不会完全保存在内存中。任何时候只有一部分(CursorWindow)在内存中。 1MB 大小限制要么(可能)是一个神话,要么是指 CursorWindow 对象,在这种情况下它是一个安全的大小
  2. 性能应该不是问题,因为游标总是在固定数量的数据上工作。可能初始查询(计算数据集的总大小,存储在mCount 变量中)可能会对感知性能产生一些影响。我需要进一步测试。

总之,很可能没有必要使用 MergeCursor 技巧或过度担心 OOM。

我本可以在源代码中进行更好的调查,但我在网上看到的内容有点欺骗了我。

【讨论】:

  • 我检查了,窗口大小是com.android.internal.R.integer.config_cursorWindowSize 千字节(参见CursorWindow 构造函数)。在我检查的来源中,该值设置为 2048,但它很可能会根据设备而变化。 Cursor 在此缓冲区中保留尽可能多的行,当它到达此窗口之外的位置时,将创建一个新的 CursorWindow 并清除前一个。很整洁。
猜你喜欢
  • 2014-10-09
  • 1970-01-01
  • 1970-01-01
  • 2012-10-08
  • 1970-01-01
  • 1970-01-01
  • 2013-09-23
  • 1970-01-01
相关资源
最近更新 更多