web-dev-qa-db-ja.com

ContentProviderでデータベースを閉じる

今週、ContentProviderについてすべて学び、SQLiteOpenHelperクラスを使用して、プロバイダー内のデータベースの作成とアップグレードを管理しました。具体的には、sdkのサンプルディレクトリからNotePadの例を読んでいます。

これで、SQLiteOpenHelperにclose()メソッドがあることがわかります。アイドル状態のデータベースを開いたままにしておくのは悪い習慣であり、メモリリークやその他の問題を引き起こす可能性があることを認識しています( this 議論が正しい方向に向かっていない限り)。 Activityでクラスを使用している場合、onDestroy()メソッドでclose()を呼び出すだけですが、私が知る限り、ContentProviderにはアクティビティと同じライフサイクルがありません。 NotePadのコードはclose()を呼び出すことはないようです。そのため、SQLiteOpenHelperまたはパズルの他の部分によって処理されると想定したいと思いますが、確かに知りたいです。サンプルコードもあまり信用していません...

質問の要約:プロバイダーのデータベースを閉じる必要がある場合は、いつ閉じますか?

73
SilithCrowe

Dianne Hackbornによる (Androidフレームワークエンジニア)コンテンツプロバイダーのデータベースを閉じる必要はありません。

コンテンツプロバイダーは、そのホスティングプロセスが作成されるときに作成され、プロセスが実行される限り存在するため、データベースを閉じる必要はありません。プロセスは強制終了されます。

これを指摘してくれた@bigstonesに感謝します。

90
philipp

この質問は少し古いですが、まだかなり関連しています。 「モダン」な方法(例えば、LoaderManagerを使用してCursorLoaderを作成し、バックグラウンドスレッドでContentProviderをクエリする)を行う場合は、 db.close() を呼び出さないでください。 ContentProvider実装。バックグラウンドスレッドでContentProviderにアクセスしようとすると、CursorLoader/AsyncTaskLoaderに関連するあらゆる種類のクラッシュが発生していましたが、db.close()呼び出しを削除することで解決しました。

したがって、次のようなクラッシュに遭遇した場合(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の実装を確認し、データベースを時期尚早に閉じていないことを確認します。 this によると、プロセスが強制終了されるとContentProviderが自動的にクリーンアップされるため、事前にデータベースを閉じる必要はありません。

とはいえ、まだ正しいことを確認してください。

  1. ContentProvider.query() から返されるカーソルを閉じます。 (CursorLoader/LoaderManagerはこれを自動的に行いますが、LoaderManagerフレームワークの外部で直接クエリを実行している場合、またはカスタムCursorLoader/AsyncTaskLoaderサブクラスを実装している場合は、カーソルをクリーンアップしていることを確認する必要があります正しく。)
  2. スレッドセーフな方法でContentProviderを実装します。 (これを行う最も簡単な方法は、データベースアクセス方法がsynchronizedブロックでラップされていることを確認することです。)
21
Steven

IveはMannaz's answerに従い、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);
    }
}
13
pleerock

データベースを自動的に閉じる場合は、開くときに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();
        }
    }
}
7
whlk

アクティビティ内でコンテンツプロバイダーを使用している場合、コンテンツプロバイダーの接続を維持する必要はないと思います。 startManagingCursorを使用して返されるカーソルオブジェクトを管理するだけで済みます。アクティビティのonPauseメソッドでは、コンテンツプロバイダーをリリースできます。 (onResumeでリロードできます)。通常、アクティビティのライフサイクルが制限されると仮定すると、これで十分です。 (少なくとも私によれば;))

0