SQLite Android数据库游标窗口分配2048 kb失败


75

我有一个例程,每秒对SQLite数据库运行多次不同的查询。过一会儿我会得到错误

"android.database.CursorWindowAllocationException: - Cursor window allocation of 2048 kb failed. # Open Cursors = " 出现在LogCat中。

我有应用日志内存的使用情况,确实,当使用情况达到一定限制时,我会收到此错误,暗示它用完了。我的直觉告诉我,每次运行查询时,数据库引擎都会创建一个NEW缓冲区(CursorWindow),即使我将游标标记为.close(),垃圾回收器和SQLiteDatabase.releaseMemory()释放内存的速度够快。我认为解决方案可能在于“强制”数据库始终写入同一缓冲区,而不创建新缓冲区,但是我一直无法找到一种方法来做到这一点。我尝试实例化自己的CursorWindow,并尝试将其设置为和SQLiteCursor无济于事。

有任何想法吗?

编辑:从@GrahamBorland重新示例代码请求:

public static CursorWindow cursorWindow = new CursorWindow("cursorWindow"); 
public static SQLiteCursor sqlCursor;
public static void getItemsVisibleArea(GeoPoint mapCenter, int latSpan, int lonSpan) {
query = "SELECT * FROM Items"; //would be more complex in real code
sqlCursor = (SQLiteCursor)db.rawQuery(query, null);
sqlCursor.setWindow(cursorWindow);
}

理想情况下,我希望能够.setWindow()在发出新查询之前将数据放入CursorWindow每次新数据的位置。


我不知道这是什么问题.. :),但我通常使SQLiteOpenHelper类成为单例。所以我从来没有发现这样的问题。
Mohsin Naeem

不,我不使用SQLiteOpenHelper,而是创建一个包含SQLiteDatabase的静态DataAccess类。这工作正常,我怀疑问题在那里。问题更多与SQLite库创建一个NEW容器来放置每个新查询的结果有关,而不是一遍又一遍地使用相同的容器。尽管可以关闭游标,但GC清理的速度比创建新容器的速度要慢,因此会产生内存消耗。
alex 2012年

您可以显示一些代码,特别是您尝试设置自己的代码CursorWindow吗?
Graham Borland

@GrahamBorland public static CursorWindow cursorWindow = new CursorWindow("cursorWindow"); public static SQLiteCursor sqlCursor; public static void getItemsVisibleArea(GeoPoint mapCenter, int latSpan, int lonSpan) { query = "SELECT * FROM Items"; //would be more complex in real code sqlCursor = (SQLiteCursor)db.rawQuery(query, null); sqlCursor.setWindow(cursorWindow); } 理想情况下,我希望能够在提供新查询之前先启用.setWindow,并在每次获得新数据时将数据放入同一CursorWindow中。
alex 2012年

3
将其编辑到您的问题中,不要将大量的代码片段粘贴为注释!
Graham Borland

Answers:


108

导致此错误的最常见原因是游标未关闭。确保在使用所有游标后将其关闭(即使发生错误)。

Cursor cursor = null;
try {
    cursor = db.query(...
    // do some work with the cursor here.
} finally {
    // this gets called even if there is an exception somewhere above
    if(cursor != null)
        cursor.close();
}

要在不关闭游标时使应用程序崩溃,可以在“应用程序”中启用“严格模式”detectLeakedSqlLiteObjectsonCreate

StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
   .detectLeakedClosableObjects()
   .detectLeakedSqlLiteObjects()
   .penaltyDeath()
   .penaltyLog()
   .build();
StrictMode.setVmPolicy(policy);

显然,您只能为调试版本启用此功能。


实际上,您可以简单地通过该示例来避免执行null和null检查。
2014年

3
就像打开文件指针一样-总是在final部分中处理关闭,以确保您的代码干净地存在。
洛特

82

如果您必须挖掘大量的SQL代码,则可以通过在MainActivity中放置以下代码段以启用StrictMode来加快调试速度。如果检测到泄漏的数据库对象,则您的应用程序现在将崩溃,日志信息突出显示泄漏的确切位置。这帮助我在几分钟内找到了流氓光标。

@Override
protected void onCreate(Bundle savedInstanceState) {
   if (BuildConfig.DEBUG) {     
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
         .detectLeakedSqlLiteObjects()
         .detectLeakedClosableObjects()
         .penaltyLog()
         .penaltyDeath()
         .build());
    }
    super.onCreate(savedInstanceState);
    ...
    ...

6
那很棒!我已经在if (BuildConfig.DEBUG) {...}主类的'static {...}'块内部制定了此标准。
布赖恩·怀特

1
到目前为止,我发现的调试此类问题的最佳,最有效的方法应该是公认的答案!
androidseb 2015年

大!但是在发行版本中,没有StrictMode是否可以正常工作?
alfdev

根据StrictMode javadoc的说法:“未来版本的Android可能会捕获更多(或更少)的操作,因此您永远不要在Google Play上分发的应用程序中启用StrictMode。”
demaksee

这是调试此类问题的最佳方法。谢谢!:)
Shivam Pokhriyal,

11

我刚刚遇到了这个问题-在有效时不关闭游标的建议答案不是我如何解决。我的问题是,当SQLite尝试重新填充其游标时,关闭数据库。我将打开数据库,查询数据库以将游标获取到数据集,然后关闭数据库并遍历游标。我注意到只要在该游标中命中某个记录,我的应用就会崩溃,并在OP中出现相同的错误。

我假设要使游标访问某些记录,它需要重新查询数据库,如果数据库已关闭,它将抛出此错误。我通过在完成所有需要的工作之前不关闭数据库来解决此问题。


5

确实,Android SQLite游标窗口可以容纳的最大大小为2MB,超过此大小将导致上述错误。通常,此错误是由在SQL数据库中以blob存储为大图像字节数组或字符串太长引起的。这是我固定的方式。

创建一个Java类,例如。FixCursorWindow并将下面的代码放入其中。

    public static void fix() {
        try {
            Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
            field.setAccessible(true);
            field.set(null, 102400 * 1024); //the 102400 is the new size added
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

现在转到您的应用程序类(如果尚未创建,请创建一个)并像这样调用FixCursorWindow

公共类应用扩展了应用{

public void onCreate()
{
    super.onCreate();
    CursorWindowFixer.fix();

}

}

最后,确保像这样将应用程序类包括在应用程序标签的清单中

    android:name=".App">

仅此而已,它现在应该可以完美运行。


你可能会得到在Android馅饼问题,是由于使用反射API
亚历Kucherenko

1

如果您正在运行Android P,则可以创建自己的游标窗口,如下所示:

if(cursor instanceof SQLiteCursor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    ((SQLiteCursor) cursor).setWindow(new CursorWindow(null, 1024*1024*10));
}

这使您可以修改特定光标的光标窗口大小,而无需借助反射。



-1

特别是在使用外部SQLite时,这是一个正常异常。您可以通过关闭Cursor Object来解决它,如下所示:

if(myCursor != null)
        myCursor.close();

意思是,如果游标有内存并且被打开然后关闭它,则应用程序将更快,所有方法将占用更少的空间,并且与数据库相关的功能也将得到改善。


-3
public class CursorWindowFixer {

  public static void fix() {
    try {
      Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
      field.setAccessible(true);
      field.set(null, 102400 * 1024);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

突破性的CursorWindow限制只有2兆字节
jingyuan iu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.