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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11340257/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 07:03:44  来源:igfitidea点击:

SQLite Android Database Cursor window allocation of 2048 kb failed

androidsqlitememorycursormemory-leaks

提问by alex

I have a routine that runs different queries against an SQLite database many times per second. After a while I would get the error

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

"android.database.CursorWindowAllocationException: - Cursor window allocation of 2048 kb failed. # Open Cursors = "appear in LogCat.

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

I had the app log memory usage, and indeed when usage reaches a certain limit the I get this error, implying it runs out. My intuition tells me that the database engine is creating a NEW buffer (CursorWindow) every time I run a query, and even though I mark the .close() the cursors, neither the garbage collector nor SQLiteDatabase.releaseMemory()are quick enough at freeing memory. I think the solution may lie in "forcing" the database to always write into the same buffer, and not create new ones, but I have been unable to find a way to do this. I have tried instantiating my own CursorWindow, and tried setting it to and SQLiteCursor to no avail.

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

?Any ideas?

?有任何想法吗?

EDIT: re example code request from @GrahamBorland:

编辑:来自@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);
}

Ideally I would like to be able to .setWindow()before giving a new query, and have the data put into the same CursorWindoweverytime I get new data.

理想情况下,我希望能够.setWindow()在给出新查询之前,并在CursorWindow每次获得新数据时将数据放入相同的数据中。

回答by whlk

Most often the cause for this error are non closed cursors. Make sure you close all cursors after using them (even in the case of an error).

大多数情况下,此错误的原因是未关闭的游标。确保在使用后关闭所有游标(即使在出现错误的情况下)。

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();
}

To make your App crash when you are not closing a cursor you can enable Strict Mode with detectLeakedSqlLiteObjectsin your Applications onCreate:

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

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

Obviously you would only enable this for debug builds.

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

回答by skyHymans

If you're having to dig through a significant amount of SQL code you may be able to speed up your debugging by putting the following code snippet in your MainActivity to enable StrictMode. If leaked database objects are detected then your app will now crash with log info highlighting exactly where your leak is. This helped me locate a rogue cursor in a matter of minutes.

如果您必须挖掘大量 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);
    ...
    ...

回答by micnguyen

I have just experienced this issue - and the the suggested answer of not closing the cursor while valid, was not how I fixed it. My issue was closing the database when SQLite was trying to repopulate it's cursor. I would open the database, query the database to get a cursor to a data set, close the database and iterate over the cursor. I noticed whenever I hit a certain record in that cursor, my app would crash with this same error in OP.

我刚刚遇到了这个问题 - 并且在有效时不关闭光标的建议答案不是我修复它的方式。我的问题是当 SQLite 试图重新填充它的游标时关闭数据库。我会打开数据库,查询数据库以获取指向数据集的游标,关闭数据库并遍历游标。我注意到每当我在该光标中点击某个记录时,我的应用程序都会在 OP 中因同样的错误而崩溃。

I assume that for the cursor to access certain records, it needs to re-query the database and if it is closed, it will throw this error. I fixed it by not closing the database until I had completed all the work I needed.

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

回答by Anuoluwapo Wahab

There is indeed a maximum size Android SQLite cursor windows can take and that is 2MB, anything more than this size would result into the above error. Mostly, this error is either caused by a large image byte array stored as blob in sql database or too long strings. Here is how i fixed it.

Android SQLite 光标窗口确实有一个最大大小,即 2MB,超过这个大小的任何东西都会导致上述错误。大多数情况下,此错误是由在 sql 数据库中存储为 blob 的大型图像字节数组或太长的字符串引起的。这是我修复它的方法。

Create a java class eg. FixCursorWindow and put below code in it.

创建一个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();
        }
    }

Now go to your application class (create one if you don't have already) and make a call to the FixCursorWindow like this

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

public class App extends Application {

公共类应用程序扩展应用程序{

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

}

}

}

Finally, ensure you include your application class in your manifest on the application tag like this

最后,确保将您的应用程序类包含在应用程序标签上的清单中,如下所示

    android:name=".App">

That's all, it should work perfectly now.

就是这样,它现在应该可以完美运行。

回答by Linus M?rtensson

If you're running Android P, you can create your own cursor window like this:

如果您运行的是 Android P,您可以像这样创建自己的光标窗口:

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

This allows you to modify the cursor window size for a specific cursor without resorting to reflections.

这允许您修改特定光标的光标窗口大小,而无需求助于反射。

回答by Sdghasemi

Here is @whlk answerwith Java 7automatic resource managementof try-finally block:

这里是@whlk答案Java 7的自动资源管理的尝试-finally块:

try (Cursor cursor = db.query(...)) {
    // do some work with the cursor here.
}

回答by Numan Gillani

This is a Normal Exception while we are using External SQLite especially. You can resolve it by closing the Cursor Object just like as follow:

当我们特别使用外部 SQLite 时,这是一个正常异常。您可以通过关闭光标对象来解决它,如下所示:

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

What it means is, IF the cursor has memory and it's opened then close it so the Application will be faster, all the Methods will take less space, and the functionalities related to the Database will also be improved.

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

回答by jingyuan iu

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();
    }
  }
}