Android SQLite DBを閉じるタイミング


96

AndroidでSQLiteデータベースを使用しています。データベースマネージャーはシングルトンで、初期化時にデータベースへの接続を開いています。誰かが私のクラスを呼び出してデータベースを操作するときにすでに開いているように、データベースをずっと開いたままにしても安全です。または、各アクセスが必要になる前後にデータベースを開いて閉じる必要があります。ずっと開いたままにしておくことに害はありますか?

ありがとう!

回答:


60

私はそれを常に開いたままにし、onStopまたはなどのライフサイクルメソッドで閉じますonDestroy。こうすることで、データベースを使用する前に毎回を呼び出すisDbLockedByCurrentThreadisDbLockedByOtherThreads、単一のSQLiteDatabaseオブジェクトでデータベースがすでに使用されているかどうかを簡単に確認できます。これにより、データベースに対する複数の操作が防止され、潜在的なクラッシュからアプリケーションが保存されます

したがって、シングルトンでは、次のようなメソッドを使用して単一のSQLiteOpenHelperオブジェクトを取得できます。

private SQLiteDatabase db;
private MyDBOpenHelper mySingletonHelperField;
public MyDBOpenHelper getDbHelper() {
    db = mySingletonHelperField.getDatabase();//returns the already created database object in my MyDBOpenHelper class(which extends `SQLiteOpenHelper`)
    while(db.isDbLockedByCurrentThread() || db.isDbLockedByOtherThreads()) {
        //db is locked, keep looping
    }
    return mySingletonHelperField;
}

オープンヘルパーオブジェクトを使用する場合は常に、このゲッターメソッドを呼び出します(スレッド化されていることを確認してください)。

シングルトンの別のメソッドは次のとおりです(上記のゲッターを呼び出そうとする前に毎回呼び出されます):

public void setDbHelper(MyDBOpenHelper mySingletonHelperField) {
    if(null == this.mySingletonHelperField) {
        this.mySingletonHelperField = mySingletonHelperField;
        this.mySingletonHelperField.setDb(this.mySingletonHelperField.getWritableDatabase());//creates and sets the database object in the MyDBOpenHelper class
    }
}

シングルトンでデータベースを閉じることもできます。

public void finalize() throws Throwable {
    if(null != mySingletonHelperField)
        mySingletonHelperField.close();
    if(null != db)
        db.close();
    super.finalize();
}

アプリケーションのユーザーが多くのデータベース相互作用を非常に迅速に作成できる場合は、上記で示したようなものを使用する必要があります。しかし、データベースの相互作用が最小限であれば、心配する必要はなく、毎回データベースを作成して閉じるだけです。


私は、このアプローチには2倍のデータベースへのアクセス(オープンデータベースを保持)、おかげでスピードアップすることができ
teh.fonsi

2
スピニングは非常に悪いテクニックです。以下の私の答えを参照してください。
ミクセル2015年

1
@ミクセル良い点。私はAPI 16が利用可能になる前にこの回答を投稿したと思いますが、間違いかもしれません
ジェームズ2015年

1
@binnyb私はそれが人々を誤解させないようにあなたの答えを更新する方が良いと思います。
ミクセル2015年

3
WARN isDbLockedByOtherThreads()は非推奨であり、API 16以降falseを返します。また、isDbLockedByCurrentThread()をチェックすると、現在のスレッドが停止し、このメソッドがtrueを返すように何も「ロック解除」できないため、無限ループが発生します。
マトレシュキン2016

20

現在のところ、データベースが別のスレッドによってロックされているかどうかを確認する必要はありません。すべてのスレッドでシングルトンSQLiteOpenHelperを使用している間は安全です。isDbLockedByCurrentThreadドキュメントから:

このメソッドの名前は、データベースへのアクティブな接続が存在することにより、スレッドがデータベースに対して実際のロックを保持していたことを意味していました。現在、特定の操作を実行するためのデータベース接続を取得できない場合、スレッドはブロックする可能性がありますが、本当の「データベースロック」はなくなりました。

isDbLockedByOtherThreads APIレベル16から廃止されました。


スレッドごとに1つのインスタンスを使用しても意味がありません。SQLiteOpenHelperはスレッドセーフです。これもメモリ効率が非常に悪いです。代わりに、アプリはデータベースごとにSQLiteOpenHelperの単一インスタンスを保持する必要があります。同時実行性を高めるには、最大4つの接続に接続プールを提供するWriteAheadLoggingを使用することをお勧めします。
ejboy

@ejboyという意味です。「スレッドごと」ではなく、「すべてのスレッドでシングルトン(= 1)SQLiteOpenHelperを使用する」。
ミクセル2018年

15

質問について:

データベースマネージャーはシングルトンで、初期化時にデータベースへの接続を開いています。

「opening DB」と「opening a connection」を分ける必要があります。SQLiteOpenHelper.getWritableDatabase()は、開かれたDBを提供します。ただし、内部で行われるため、接続を制御する必要はありません。

誰かが私のクラスを呼び出してデータベースを操作するときにすでに開いているように、データベースをずっと開いたままにしても安全です。

はい、そうです。トランザクションが適切に閉じられている場合、接続はハングしません。GCがファイナライズすると、DBも自動的に閉じられることに注意してください。

または、各アクセスが必要になる前と後にデータベースを開いて閉じる必要があります。

SQLiteDatabaseインスタンスを閉じても、接続を閉じる以外に大きな効果はありませんが、現時点でいくつかの接続がある場合、これは開発者の悪いことです。また、SQLiteDatabase.close()の後、SQLiteOpenHelper.getWritableDatabase()は新しいインスタンスを返します。

それをずっと開いたままにしておくことに害はありますか?

いいえ、ありません。また、たとえばActivity.onStop()などの無関係な瞬間およびスレッドでDBを閉じると、アクティブな接続が閉じられ、データが不整合な状態になる可能性があることにも注意してください。


「SQLiteDatabase.close()の後、SQLiteOpenHelper.getWritableDatabase()は新しいインスタンスを返す」という言葉を読んだ後、ありがとうございます。アプリケーションの古い問題に対する答えがようやくわかりました。Android 9への移行後に重大になった問題(stackoverflow.com/a/54224922/297710を参照)
yvolk


1

パフォーマンスの観点から、最適な方法は、アプリケーションレベルでSQLiteOpenHelperの単一インスタンスを維持することです。データベースを開くことは、コストがかかる可能性があり、ブロッキング操作であるため、メインスレッドやアクティビティライフサイクルメソッドでは実行しないでください。

データベースが使用されていない場合、setIdleConnectionTimeout()メソッド(Android 8.1で導入)を使用してRAMを解放できます。アイドルタイムアウトが設定されている場合、非アクティブ状態が一定期間続いた後、つまりデータベースにアクセスしなかった場合に、データベース接続が閉じられます。新しいクエリが実行されると、接続はアプリに対して透過的に再開されます。

それに加えて、アプリは、バックグラウンドになったとき、またはonTrimMemory()などでメモリプレッシャーを検出したときに、releaseMemory()を呼び出すことができます。



-9

独自のアプリケーションコンテキストを作成し、そこからデータベースを開いて閉じます。そのオブジェクトには、接続を閉じるために使用できるOnTerminate()メソッドもあります。私はまだ試していませんが、より良いアプローチのようです。

@binnyb:finalize()を使用して接続を閉じるのは好きではありません。うまくいくかもしれませんが、Javaのfinalize()メソッドでコードを書くことは私が理解していることから、悪い考えです。


いつ呼び出されるかわからないため、ファイナライズに依存したくありません。オブジェクトは、GCがオブジェクトをクリーンアップすることを決定するまでlimboにとどまることができます。その後、finalize()が呼び出されます。それが役に立たない理由です。これを行う正しい方法は、オブジェクトが使用されなくなったときに(ガベージコレクションされているかどうかに関係なく)呼び出されるメソッドを持つことであり、まさにそれがonTerminate()と同じです。
erwan

5
ドキュメントは、かなりはっきり言ってonTerminate、本番環境で呼び出されることはありません- developer.android.com/reference/android/app/...
エリエゼル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.