web-dev-qa-db-ja.com

SQLite Android 2048 kbのデータベースカーソルウィンドウの割り当てに失敗しました

1秒間にSQLiteデータベースに対してさまざまなクエリを実行するルーチンがあります。しばらくするとエラーが表示されます

_"Android.database.CursorWindowAllocationException: - Cursor window allocation of 2048 kb failed. # Open Cursors = "_がLogCatに表示されます。

アプリのメモリ使用量をログに記録しましたが、実際に使用量が特定の制限に達すると、このエラーが発生し、それがなくなることを意味します。私の直感では、クエリを実行するたびにデータベースエンジンが新しいバッファー(CursorWindow)を作成し、.close()カーソルにマークを付けても、ガベージコレクターとSQLiteDatabase.releaseMemory()のどちらも高速ではないことがわかりますメモリを解放するのに十分です。解決策は、データベースを常に同じバッファに「強制」書き込みし、新しいバッファを作成しないことにあると思いますが、これを行う方法を見つけることができませんでした。私は自分のCursorWindowのインスタンスを作成しようとしました。

何か案は?

編集:@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に入れたいと思います。

64
alex

ほとんどの場合、このエラーの原因は、閉じられていないカーソルです。使用した後は、すべてのカーソルを必ず閉じてください(エラーが発生した場合でも)。

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

大量の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);
    ...
    ...
79
skyjacks

私はちょうどこの問題を経験しました-そして、有効な間カーソルを閉じないという提案された答えは、私がそれを修正した方法ではありませんでした。私の問題は、SQLiteがカーソルを再作成しようとしたときにデータベースを閉じることでした。データベースを開き、データベースを照会してデータセットへのカーソルを取得し、データベースを閉じて、カーソルを反復処理します。そのカーソルで特定のレコードにヒットするたびに、OPで同じエラーが発生してアプリがクラッシュすることに気付きました。

カーソルが特定のレコードにアクセスするには、データベースを再クエリする必要があり、データベースが閉じている場合、このエラーがスローされると想定しています。必要な作業がすべて完了するまでデータベースを閉じないことで修正しました。

11
micnguyen

確かに最大サイズがあります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を呼び出します。

パブリッククラスAppはApplication {を拡張します

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

}

}

最後に、このようにアプリケーションタグのマニフェストにアプリケーションクラスを含めるようにしてください

    Android:name=".App">

これですべて、今は完全に機能するはずです。

3

Android Pを実行している場合、次のように独自のカーソルウィンドウを作成できます。

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

これにより、反射に頼らずに特定のカーソルのカーソルウィンドウサイズを変更できます。

0