code

2048KB의 SQLite Android 데이터베이스 커서 창 할당 실패

codestyles 2020. 11. 16. 08:21
반응형

2048KB의 SQLite Android 데이터베이스 커서 창 할당 실패


SQLite 데이터베이스에 대해 초당 여러 번 다른 쿼리를 실행하는 루틴이 있습니다. 잠시 후 오류가 발생합니다.

"android.database.CursorWindowAllocationException: - Cursor window allocation of 2048 kb failed. # Open Cursors = " LogCat에 나타납니다.

나는 앱 로그 메모리 사용량이 있었고 실제로 사용량이 특정 한도에 도달하면이 오류가 발생하여 소진되었음을 의미합니다. 내 직감에 따르면 쿼리를 실행할 때마다 데이터베이스 엔진이 새 버퍼 (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있고 새 데이터를 얻을 때마다 데이터를 동일하게 입력하고 싶습니다.


대부분의 경우이 오류의 원인은 닫히지 않은 커서입니다. 커서를 사용한 후에는 모든 커서를 닫아야합니다 (오류가 발생하더라도).

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

커서를 닫지 않을 때 앱 충돌을 일으키 려면 응용 프로그램에서 Strict Mode를detectLeakedSqlLiteObjects 활성화 할 수 있습니다 onCreate.

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

분명히 디버그 빌드에 대해서만 이것을 활성화합니다.


상당한 양의 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);
    ...
    ...

나는 방금이 문제를 경험했으며 유효한 동안 커서를 닫지 않는 제안 된 대답은 내가 그것을 고치는 방법이 아닙니다. 내 문제는 SQLite가 커서를 다시 채우려 고 할 때 데이터베이스를 닫는 것입니다. 데이터베이스를 열고 데이터베이스를 쿼리하여 데이터 세트에 커서를 가져오고 데이터베이스를 닫고 커서를 반복합니다. 해당 커서에서 특정 레코드를 칠 때마다 내 앱이 OP에서 이와 동일한 오류로 충돌하는 것을 알았습니다.

커서가 특정 레코드에 액세스하려면 데이터베이스를 다시 쿼리해야하며 닫히면이 오류가 발생합니다. 필요한 모든 작업을 완료 할 때까지 데이터베이스를 닫지 않음으로써 문제를 해결했습니다.


실제로 Android SQLite 커서 창에 사용할 수있는 최대 크기는 2MB입니다.이 크기를 초과하면 위의 오류가 발생합니다. 대부분이 오류는 SQL 데이터베이스에 Blob으로 저장된 큰 이미지 바이트 배열이나 너무 긴 문자열로 인해 발생합니다. 다음은 내가 수정 한 방법입니다.

예를 들어 자바 클래스를 만듭니다. 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 extends Application {

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

}

}

마지막으로 다음과 같이 애플리케이션 태그의 매니페스트에 애플리케이션 클래스를 포함해야합니다.

    android:name=".App">

That's all, it should work perfectly now.


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

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.


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

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

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:

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.


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

참고URL : https://stackoverflow.com/questions/11340257/sqlite-android-database-cursor-window-allocation-of-2048-kb-failed

반응형