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

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

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

我有应用程序记录内存使用情况,实际上,当使用达到某个限制时,我得到这个错误,暗示它用完了。我的直觉告诉我,数据库引擎正在创建一个新的缓冲区(CursorWindow) ,每次我运行一个查询,即使。Close ()游标,垃圾收集器和 SQLiteDatabase.releaseMemory()释放内存的速度都不够快。我认为解决方案可能在于“强制”数据库总是写入同一个缓冲区,而不是创建新的缓冲区,但我一直无法找到这样做的方法。我尝试实例化我自己的 CursorWindow,并尝试将 SQLiteCursor 设置为它,但是没有用。

¿Any ideas?

编辑: 重新示例来自@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每次我得到新的数据。

65926 次浏览

出现此错误的原因通常是非关闭游标。确保在使用所有游标后关闭它们(即使出现错误)。

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

为了让你的应用程序崩溃,当你没有关闭光标时,你可以在你的应用程序 onCreate中启用 Strict Mode with detectLeakedSqlLiteObjects:

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

显然,您只会为调试版本启用此选项。

我刚刚经历了这个问题——建议的答案是在有效时不关闭光标,而不是我如何修复它。当 SQLite 试图重新填充它的游标时,我的问题是关闭数据库。我将打开数据库,查询数据库以获得指向数据集的游标,关闭数据库并在游标上迭代。我注意到每当我在那个光标中打到某个记录时,我的应用程序就会在 OP 中崩溃,出现同样的错误。

我假设光标要访问某些记录,它需要重新查询数据库,如果数据库关闭,它将抛出此错误。我修复了它,没有关闭数据库,直到我完成所有的工作,我需要。

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.

@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate(savedInstanceState);
...
...
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();
}
}
}

确实有一个 Android SQLite 游标窗口可以使用的最大大小,即2MB,超过这个大小就会导致上述错误。这个错误主要是由于 sql 数据库中存储为 blob 的大型图像字节数组或过长的字符串造成的。我是这么修的。

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

    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

公共类 App 扩展应用程序{

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


}

}

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

    android:name=".App">

就是这样,现在应该可以正常工作了。

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

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

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

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

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

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

Here is @whlk answer with 爪哇7 automatic resource management of try-finally block:

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