Android SQLite DB Wann schließen?

96

Ich arbeite mit einer SQLite-Datenbank auf Android. Mein Datenbankmanager ist ein Singleton und öffnet gerade eine Verbindung zur Datenbank, wenn diese initialisiert wird. Es ist sicher, die Datenbank die ganze Zeit geöffnet zu lassen, damit sie bereits geöffnet ist, wenn jemand meine Klasse anruft, um mit der Datenbank zu arbeiten. Oder sollte ich die Datenbank vor und nach jedem Zugriff öffnen und schließen? Kann es schaden, es die ganze Zeit offen zu lassen?

Vielen Dank!

w.donahue
quelle

Antworten:

60

Ich würde es die ganze Zeit offen halten und es in einer Lebenszyklusmethode wie onStopoder schließen onDestroy. Auf diese Weise können Sie leicht überprüfen , ob die Datenbank durch den Aufruf bereits verwendet wird isDbLockedByCurrentThreadoder isDbLockedByOtherThreadsauf dem einzelnen SQLiteDatabaseObjekt jedes Mal , bevor Sie es benutzen. Dies verhindert mehrfache Manipulationen an der Datenbank und schützt Ihre Anwendung vor einem möglichen Absturz

In Ihrem Singleton haben Sie möglicherweise eine Methode wie diese, um Ihr einzelnes SQLiteOpenHelperObjekt zu erhalten:

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

Wenn Sie also Ihr offenes Hilfsobjekt verwenden möchten, rufen Sie diese Getter-Methode auf (stellen Sie sicher, dass es einen Thread hat).

Eine andere Methode in Ihrem Singleton ist möglicherweise (JEDES MAL aufgerufen, bevor Sie versuchen, den obigen Getter aufzurufen):

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
    }
}

Möglicherweise möchten Sie die Datenbank auch im Singleton schließen:

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

Wenn die Benutzer Ihrer Anwendung in der Lage sind, sehr schnell viele Datenbankinteraktionen zu erstellen, sollten Sie etwas verwenden, wie ich oben gezeigt habe. Aber wenn es nur minimale Datenbankinteraktionen gibt, würde ich mir darüber keine Sorgen machen und die Datenbank jedes Mal erstellen und schließen.

James
quelle
Ich könnte den Datenbankzugriff mit diesem Ansatz um den Faktor 2 beschleunigen (Datenbank offen halten), danke
teh.fonsi
2
Spinnen ist eine sehr schlechte Technik. Siehe meine Antwort unten.
Mixel
1
@mixel guter Punkt. Ich glaube, ich habe diese Antwort gepostet, bevor API 16 verfügbar war, aber ich könnte mich irren
James
1
@binnyb Ich denke, es ist besser, deine Antwort zu aktualisieren, damit sie die Leute nicht irreführt.
Mixel
3
WARN isDbLockedByOtherThreads () ist Depricated und gibt seit API 16 false zurück. Wenn Sie auch isDbLockedByCurrentThread () überprüfen, wird eine Endlosschleife ausgegeben, da der aktuelle Thread gestoppt wird und nichts die Datenbank entsperren kann, sodass diese Methode true zurückgibt.
Matreshkin
20

Ab sofort muss nicht mehr überprüft werden, ob die Datenbank von einem anderen Thread gesperrt wurde. Während Sie Singleton SQLiteOpenHelper in jedem Thread verwenden, sind Sie sicher. Aus der isDbLockedByCurrentThreadDokumentation:

Der Name dieser Methode stammt aus einer Zeit, in der eine aktive Verbindung zur Datenbank bedeutete, dass der Thread eine tatsächliche Sperre für die Datenbank hatte. Heutzutage gibt es keine echte "Datenbanksperre" mehr, obwohl Threads blockieren können, wenn sie keine Datenbankverbindung herstellen können, um eine bestimmte Operation auszuführen.

isDbLockedByOtherThreads ist seit API Level 16 veraltet.

mischen
quelle
Es macht keinen Sinn, eine Instanz pro Thread zu verwenden. SQLiteOpenHelper ist threadsicher. Dies ist auch sehr speichereffizient. Stattdessen sollte eine App eine einzelne Instanz von SQLiteOpenHelper pro Datenbank behalten. Für eine bessere Parallelität wird empfohlen, WriteAheadLogging zu verwenden, das Verbindungspooling für bis zu 4 Verbindungen bietet.
Ejboy
@jboy Ich meinte das. "Verwenden Sie singleton (= one) SQLiteOpenHelper in jedem Thread", nicht "pro Thread".
Mixel
15

Zu den Fragen:

Mein Datenbankmanager ist ein Singleton und öffnet gerade eine Verbindung zur Datenbank, wenn diese initialisiert wird.

Wir sollten 'DB öffnen', 'Verbindung öffnen' teilen. SQLiteOpenHelper.getWritableDatabase () gibt eine geöffnete Datenbank an. Wir müssen jedoch keine Verbindungen steuern, da dies intern erfolgt.

Es ist sicher, die Datenbank die ganze Zeit geöffnet zu lassen, damit sie bereits geöffnet ist, wenn jemand meine Klasse anruft, um mit der Datenbank zu arbeiten.

Ja, so ist es. Verbindungen hängen nicht, wenn Transaktionen ordnungsgemäß geschlossen werden. Beachten Sie, dass Ihre Datenbank auch automatisch geschlossen wird, wenn GC sie abschließt.

Oder sollte ich die Datenbank vor und nach jedem Zugriff öffnen und schließen?

Das Schließen der SQLiteDatabase-Instanz bietet nichts Außergewöhnliches als das Schließen von Verbindungen. Dies ist jedoch für Entwickler schlecht, wenn derzeit einige Verbindungen bestehen. Nach SQLiteDatabase.close () gibt SQLiteOpenHelper.getWritableDatabase () eine neue Instanz zurück.

Kann es schaden, es die ganze Zeit offen zu lassen?

Nein, gibt es nicht. Beachten Sie auch, dass das Schließen der Datenbank zu einem nicht verwandten Zeitpunkt und Thread, z. B. in Activity.onStop (), aktive Verbindungen schließen und die Daten in einem inkonsistenten Zustand belassen kann.

Matreshkin
quelle
Vielen Dank, nachdem ich Ihre Worte "Nach SQLiteDatabase.close () wird SQLiteOpenHelper.getWritableDatabase () eine neue Instanz zurückgeben" gelesen habe, wurde mir klar, dass ich endlich eine Antwort auf ein altes Problem meiner Anwendung habe. Zu einem Problem, das nach der Migration auf Android 9 kritisch wurde (siehe stackoverflow.com/a/54224922/297710 )
yvolk
1

Aus Sicht der Leistung besteht der optimale Weg darin, eine einzelne Instanz von SQLiteOpenHelper auf Anwendungsebene zu belassen. Das Öffnen einer Datenbank kann teuer sein und ist ein Blockierungsvorgang. Daher sollte dies nicht im Hauptthread und / oder in den Aktivitätslebenszyklusmethoden durchgeführt werden.

Die in Android 8.1 eingeführte Methode setIdleConnectionTimeout () kann verwendet werden, um RAM freizugeben, wenn die Datenbank nicht verwendet wird. Wenn das Leerlaufzeitlimit festgelegt ist, werden die Datenbankverbindungen nach einer Zeit der Inaktivität geschlossen, dh wenn nicht auf die Datenbank zugegriffen wurde. Verbindungen werden für die App transparent wieder geöffnet, wenn eine neue Abfrage ausgeführt wird.

Darüber hinaus kann eine App releaseMemory () aufrufen, wenn sie in den Hintergrund tritt oder Speicherdruck erkennt, z. B. in onTrimMemory ()

Ejboy
quelle
0

Sie können auch ContentProvider verwenden. Es wird dieses Zeug für Sie tun.

Gregory Buiko
quelle
-9

Erstellen Sie Ihren eigenen Anwendungskontext und öffnen und schließen Sie die Datenbank von dort aus. Dieses Objekt verfügt auch über eine OnTerminate () -Methode, mit der Sie die Verbindung schließen können. Ich habe es noch nicht versucht, aber es scheint ein besserer Ansatz zu sein.

@binnyb: Ich mag es nicht, finalize () zu verwenden, um die Verbindung zu schließen. Könnte funktionieren, aber nach meinem Verständnis ist das Schreiben von Code in einer Java finalize () -Methode eine schlechte Idee.

Leander
quelle
Sie möchten sich nicht auf finalize verlassen, da Sie nie wissen, wann es aufgerufen wird. Ein Objekt kann in der Schwebe bleiben, bis der GC beschließt, es zu reinigen, und erst dann wird finalize () aufgerufen. Deshalb ist es nutzlos. Der richtige Weg, dies zu tun, besteht darin, eine Methode zu haben, die aufgerufen wird, wenn das Objekt nicht mehr verwendet wird (unabhängig davon, ob Müll gesammelt wird oder nicht), und genau das ist onTerminate ().
Erwan
5
Die Ärzte sagen ziemlich deutlich, dass onTerminatedies in einer Produktionsumgebung nicht aufgerufen wird - developer.android.com/reference/android/app/…
Eliezer