developer tip

ContentProvider에서 데이터베이스 닫기

optionbox 2020. 10. 24. 10:03
반응형

ContentProvider에서 데이터베이스 닫기


이번 주에는 ContentProvider에 대한 모든 것을 배우고 SQLiteOpenHelper 클래스를 사용하여 공급자 내부의 데이터베이스 생성 및 업그레이드를 관리했습니다. 특히, sdk의 샘플 디렉토리에서 메모장 예제를 읽었습니다.

이제 SQLiteOpenHelper에 close () 메서드가 있음을 알 수 있습니다. 유휴 데이터베이스를 열어 두는 것은 나쁜 습관이며 메모리 누수를 유발할 수 있다는 것을 알고 있습니다 ( 논의가 올바른 방향으로 진행 되지 않는 한 ). 액티비티에서 클래스를 사용하고 있다면 onDestroy () 메서드에서 close ()를 호출하기 만하면되지만 내가 아는 한 ContentProvider에는 액티비티와 동일한 라이프 사이클이 없습니다. NotePad의 코드는 close ()를 호출하는 것 같지 않으므로 SQLiteOpenHelper 또는 다른 퍼즐 조각에 의해 처리된다고 가정하고 싶지만 확실하게 알고 싶습니다. 나는 샘플 코드를 그다지 신뢰하지 않는다.

질문 요약 : 공급자의 데이터베이스를 언제 닫아야합니까?


Dianne Hackborn (Android 프레임 워크 엔지니어) 에 따르면 콘텐츠 제공 업체에서 데이터베이스를 닫을 필요가 없습니다.

콘텐츠 제공자는 호스팅 프로세스가 생성 될 때 생성되고 프로세스가 진행되는 동안 계속 유지되므로 데이터베이스를 닫을 필요가 없습니다. 커널의 일부로 닫히게됩니다. 프로세스가 종료됩니다.

이것을 지적 해 주신 @bigstones에게 감사드립니다.


이 질문은 약간 오래되었지만 여전히 관련성이 높습니다. '현대적인'방식으로 작업하는 경우 (예 : LoaderManager를 사용하고 백그라운드 스레드에서 ContentProvider를 쿼리하기 위해 CursorLoader를 생성하는 경우 ) ContentProvider 구현에서 db.close ()호출하지 않도록하십시오 . db.close () 호출을 제거하여 해결 된 백그라운드 스레드에서 ContentProvider에 액세스하려고 할 때 CursorLoader / AsyncTaskLoader와 관련된 모든 종류의 충돌이 발생했습니다.

따라서 다음과 같은 충돌이 발생하는 경우 (Jelly Bean 4.1.1) :

Caused by: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
    at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962)
    at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:677)
    at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348)
    at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894)
    at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834)
    at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143)
    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
    at android.content.ContentResolver.query(ContentResolver.java:388)
    at android.content.ContentResolver.query(ContentResolver.java:313)
    at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:147)
    at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:1)
    at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240)
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51)
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40)
    at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
    ... 4 more

또는이 (ICS 4.0.4) :

Caused by: java.lang.IllegalStateException: database /data/data/com.hindsightlabs.paprika/databases/Paprika.db (conn# 0) already closed
    at android.database.sqlite.SQLiteDatabase.verifyDbIsOpen(SQLiteDatabase.java:2215)
    at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:436)
    at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:422)
    at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:79)
    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164)
    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:156)
    at android.content.ContentResolver.query(ContentResolver.java:318)
    at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:49)
    at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:35)
    at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240)
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51)
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40)
    at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
    ... 4 more

또는 LogCat에 다음과 같은 오류 메시지가 표시되는 경우 :

Cursor: invalid statement in fillWindow()

그런 다음 ContentProvider 구현을 확인하고 데이터베이스를 너무 일찍 닫지 않았는지 확인하십시오. 에 따르면 과정이 어쨌든 살해 할 때 시간 앞서 데이터베이스를 종료 할 필요가 없습니다, 컨텐트 프로는 자동으로 정리 얻을 것이다.

즉, 여전히 올바른지 확인하십시오.

  1. ContentProvider.query () 에서 반환 된 커서를 닫습니다 . (CursorLoader / LoaderManager는이 작업을 자동으로 수행하지만 LoaderManager 프레임 워크 외부에서 직접 쿼리를 수행하거나 사용자 정의 CursorLoader / AsyncTaskLoader 하위 클래스를 구현 한 경우 커서를 정리해야합니다. 정확히.)
  2. 스레드로부터 안전한 방식으로 ContentProvider를 구현합니다. (이 작업을 수행하는 가장 쉬운 방법은 데이터베이스 액세스 방법이 동기화 된 블록에 래핑되어 있는지 확인하는 것 입니다.)

나는 Mannaz의 대답을 따르고 SQLiteCursor(database, driver, table, query);생성자가 더 이상 사용되지 않는 것을 보았습니다 . 그런 다음 getDatabase()메서드를 찾아 mDatabase포인터 대신 사용했습니다 . 역방향 기능을위한 생성자 유지

public class MyOpenHelper extends SQLiteOpenHelper {
    public static final String TAG = "MyOpenHelper";

    public static final String DB_NAME = "myopenhelper.db";
    public static final int DB_VESRION = 1;

    public MyOpenHelper(Context context) {
        super(context, DB_NAME, new LeaklessCursorFactory(), DB_VESRION);
    }

    //...
}

public class LeaklessCursor extends SQLiteCursor {
    static final String TAG = "LeaklessCursor";

    public LeaklessCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
            String editTable, SQLiteQuery query) {
        super(db, driver, editTable, query);
    }

    @Override
    public void close() {
        final SQLiteDatabase db = getDatabase();
        super.close();
        if (db != null) {
            Log.d(TAG, "Closing LeaklessCursor: " + db.getPath());
            db.close();
        }
    }
}


public class LeaklessCursorFactory implements CursorFactory {
    @Override
    public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
        String editTable, SQLiteQuery query) {
        return new LeaklessCursor(db,masterQuery,editTable,query);
    }
}

데이터베이스를 자동으로 닫으려면 CursorFactory열 때 다음을 제공 할 수 있습니다 .

mContext.openOrCreateDatabase(DB_NAME, SQLiteDatabase.OPEN_READWRITE, new LeaklessCursorFactory());

수업은 다음과 같습니다.

public class LeaklessCursorFactory implements CursorFactory {
    @Override
    public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
        String editTable, SQLiteQuery query) {
        return new LeaklessCursor(db,masterQuery,editTable,query);
    }
}


public class LeaklessCursor extends SQLiteCursor {
    static final String TAG = "LeaklessCursor";
    final SQLiteDatabase mDatabase;

    public LeaklessCursor(SQLiteDatabase database, SQLiteCursorDriver driver, String table, SQLiteQuery query) {
        super(database, driver, table, query);
        mDatabase = database;
    }

    @Override
    public void close() {
        Log.d(TAG, "Closing LeaklessCursor: " + mDatabase.getPath());
        super.close();
        if (mDatabase != null) {
            mDatabase.close();
        }
    }
}

Close it when you are done with it, preferably in a finally block so you can ensure that it happens. I know that sounds a little trite and off-the-cuff, but it's really the only answer that I know of. If you open the database and perform an action, close it when you're done with that action unless you know for a fact it will be needed again (in which case be sure to close it once its no longer needed).


If you are using your content provider within a activity, then I do not believe that you have to maintain the connection of the content provider. You could just manage the cursor object returned using startManagingCursor. In the onPause method of activity, you can release the content provider. ( you can reload it in onResume). Assuming that the activity life cycle will usually be limited, this would suffice. (Atleast according to me ;))

참고URL : https://stackoverflow.com/questions/4547461/closing-the-database-in-a-contentprovider

반응형