Quando chiudere il DB SQLite di Android


96

Sto lavorando con un database SQLite su Android. Il mio gestore di database è un singleton e in questo momento apre una connessione al database quando viene inizializzato. È sicuro lasciare il database aperto tutto il tempo in modo che quando qualcuno chiama la mia classe per lavorare con il database è già aperto? O devo aprire e chiudere il database prima e dopo ogni accesso è necessario. C'è qualcosa di male nel lasciarlo aperto tutto il tempo?

Grazie!

Risposte:


60

Lo terrei aperto per tutto il tempo e lo chiuderò con un metodo del ciclo di vita come onStopo onDestroy. in questo modo, puoi facilmente verificare se il database è già in uso chiamando isDbLockedByCurrentThreado isDbLockedByOtherThreadssul singolo SQLiteDatabaseoggetto ogni volta prima di utilizzarlo. ciò impedirà più manipolazioni al database e salverà l'applicazione da un potenziale arresto anomalo

quindi nel tuo singleton, potresti avere un metodo come questo per ottenere il tuo singolo SQLiteOpenHelperoggetto:

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

quindi ogni volta che vuoi usare il tuo oggetto helper aperto, chiama questo metodo getter (assicurati che sia filettato)

un altro metodo nel tuo singleton potrebbe essere (chiamato OGNI VOLTA prima di provare a chiamare il getter sopra):

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

potresti anche voler chiudere il database nel singleton:

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

se gli utenti della tua applicazione hanno la capacità di creare molte interazioni con il database molto rapidamente, dovresti usare qualcosa come ho dimostrato sopra. ma se ci sono interazioni con il database minime, non me ne preoccuperei e creo e chiudi il database ogni volta.


Potrei accelerare l'accesso al database di un fattore 2 con questo approccio (mantieni il database aperto), grazie
teh.fonsi

2
Lo spinning è una tecnica pessima. Vedi la mia risposta di seguito.
mixel

1
@mixel buon punto. Credo di aver pubblicato questa risposta prima che l'API 16 fosse disponibile, ma potrei sbagliarmi
james

1
@binnyb Penso che sia meglio aggiornare la tua risposta in modo da non fuorviare le persone.
mixel

3
WARN isDbLockedByOtherThreads () è deprecato e restituisce false dall'API 16. Anche il controllo di isDbLockedByCurrentThread () darà un ciclo infinito perché interrompe il thread corrente e niente può 'sbloccare DB' in modo che questo metodo restituisca true.
matreshkin

20

A partire da ora non è necessario verificare se il database è bloccato da un altro thread. Mentre usi singleton SQLiteOpenHelper in ogni thread sei al sicuro. Dalla isDbLockedByCurrentThreaddocumentazione:

Il nome di questo metodo deriva da un momento in cui avere una connessione attiva al database significava che il thread deteneva un blocco effettivo sul database. Al giorno d'oggi, non esiste più un vero "blocco del database" sebbene i thread possano bloccarsi se non possono acquisire una connessione al database per eseguire una particolare operazione.

isDbLockedByOtherThreads è deprecato dal livello API 16.


Non ha senso usare un'istanza per thread. SQLiteOpenHelper è thread-safe. Anche questo è molto inefficiente per la memoria. Invece un'app dovrebbe mantenere una singola istanza di SQLiteOpenHelper per database. Per una migliore concorrenza, si consiglia di utilizzare WriteAheadLogging che fornisce il pool di connessioni per un massimo di 4 connessioni.
ejboy

@ejboy, intendevo questo. "Usa singleton (= uno) SQLiteOpenHelper in ogni thread", non "per thread".
mixel

15

Per quanto riguarda le domande:

Il mio gestore di database è un singleton e in questo momento apre una connessione al database quando viene inizializzato.

Dovremmo dividere "apertura DB", "apertura di una connessione". SQLiteOpenHelper.getWritableDatabase () fornisce un DB aperto. Ma non dobbiamo controllare le connessioni poiché avviene internamente.

È sicuro lasciare il database aperto tutto il tempo in modo che quando qualcuno chiama la mia classe per lavorare con il database è già aperto?

Sì. Le connessioni non si bloccano se le transazioni vengono chiuse correttamente. Tieni presente che il tuo DB verrà chiuso automaticamente anche se GC lo finalizza.

O devo aprire e chiudere il database prima e dopo ogni accesso è necessario.

La chiusura dell'istanza di SQLiteDatabase non dà nulla di eccezionale tranne la chiusura delle connessioni, ma questo è un problema per lo sviluppatore se ci sono alcune connessioni in questo momento. Inoltre, dopo SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () restituirà una nuova istanza.

C'è qualcosa di male nel lasciarlo aperto tutto il tempo?

No, non c'è. Si noti inoltre che la chiusura del DB in un momento e thread non correlati, ad esempio in Activity.onStop (), potrebbe chiudere le connessioni attive e lasciare i dati in uno stato incoerente.


Grazie, dopo aver letto le tue parole "dopo SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () restituirà una nuova istanza" Mi sono reso conto che finalmente ho una risposta a un vecchio problema della mia applicazione. Per un problema, che è diventata critica dopo la migrazione ad Android 9 (vedi stackoverflow.com/a/54224922/297710 )
yvolk


1

Dal punto di vista delle prestazioni, il modo ottimale è mantenere una singola istanza di SQLiteOpenHelper a livello di applicazione. L'apertura del database può essere costosa ed è un'operazione di blocco, quindi non dovrebbe essere eseguita sul thread principale e / o nei metodi del ciclo di vita dell'attività.

Il metodo setIdleConnectionTimeout () (introdotto in Android 8.1) può essere utilizzato per liberare RAM quando il database non viene utilizzato. Se è impostato il timeout di inattività, le connessioni al database verranno chiuse dopo un periodo di inattività, ovvero quando non è stato effettuato l'accesso al database. Le connessioni verranno riaperte in modo trasparente all'app, quando viene eseguita una nuova query.

In aggiunta a ciò, un'app può chiamare releaseMemory () quando passa in background o rileva la pressione della memoria, ad esempio in onTrimMemory ()



-9

Crea il tuo contesto dell'applicazione, quindi apri e chiudi il database da lì. Quell'oggetto ha anche un metodo OnTerminate () che potresti usare per chiudere la connessione. Non l'ho ancora provato ma mi sembra un approccio migliore.

@binnyb: non mi piace usare finalize () per chiudere la connessione. Potrebbe funzionare, ma da quello che ho capito scrivere codice in un metodo Java finalize () è una cattiva idea.


Non vuoi fare affidamento sulla finalizzazione perché non sai mai quando verrà chiamata. Un oggetto può rimanere nel limbo fino a quando il GC non decide di pulirlo, e solo allora verrà chiamato finalize (). Ecco perché è inutile. Il modo corretto per farlo è avere un metodo che viene chiamato quando l'oggetto non viene più utilizzato (indipendentemente dal fatto che sia stato raccolto o meno dalla spazzatura), ed è esattamente ciò che è onTerminate ().
Erwan

5
I documenti dicono abbastanza chiaramente che onTerminatenon verrà chiamato in un ambiente di produzione - developer.android.com/reference/android/app/…
Eliezer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.