Android SQLite DB Cuándo cerrar

96

Estoy trabajando con una base de datos SQLite en Android. Mi administrador de base de datos es un singleton y ahora mismo abre una conexión a la base de datos cuando se inicializa. ¿Es seguro dejar la base de datos abierta todo el tiempo para que cuando alguien llame a mi clase para trabajar con la base de datos ya esté abierta? ¿O debo abrir y cerrar la base de datos antes y después de que se necesite cada acceso? ¿Hay algún daño en dejarlo abierto todo el tiempo?

¡Gracias!

w.donahue
fuente

Respuestas:

60

Lo mantendría abierto todo el tiempo y lo cerraría en algún método de ciclo de vida como onStopo onDestroy. de esa manera, puede verificar fácilmente si la base de datos ya está en uso llamando isDbLockedByCurrentThreado isDbLockedByOtherThreadsen el SQLiteDatabaseobjeto individual cada vez que lo use. esto evitará múltiples manipulaciones en la base de datos y salvará su aplicación de un posible bloqueo

por lo que en su singleton, es posible que tenga un método como este para obtener su único SQLiteOpenHelperobjeto:

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

así que siempre que desee utilizar su objeto auxiliar abierto, llame a este método getter (asegúrese de que esté enhebrado)

otro método en su singleton puede ser (llamado CADA VEZ antes de intentar llamar al getter anterior):

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

es posible que también desee cerrar la base de datos en el singleton:

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

Si los usuarios de su aplicación tienen la capacidad de crear muchas interacciones con la base de datos muy rápidamente, debe usar algo como lo que he demostrado anteriormente. pero si hay interacciones mínimas con la base de datos, no me preocuparía por eso, y solo crearía y cerraría la base de datos cada vez.

James
fuente
Podría acelerar el acceso a la base de datos en un factor de 2 con este enfoque (mantener la base de datos abierta), gracias
teh.fonsi
2
Spinning es una técnica muy mala. Vea mi respuesta a continuación.
mixel
1
@mixel buen punto. Creo que publiqué esta respuesta antes de que API 16 estuviera disponible, pero podría estar equivocado
james
1
@binnyb Creo que es mejor actualizar tu respuesta para que no engañe a la gente.
mixel
3
WARN isDbLockedByOtherThreads () está Depricated y devuelve falso desde API 16. También comprobar isDbLockedByCurrentThread () dará un bucle infinito porque detiene el hilo actual y nada puede 'desbloquear DB' para que este método devuelva verdadero.
matreshkin
20

A partir de ahora, no es necesario verificar si la base de datos está bloqueada por otro hilo. Si bien usa singleton SQLiteOpenHelper en cada hilo, está seguro. De la isDbLockedByCurrentThreaddocumentación:

El nombre de este método proviene de un momento en el que tener una conexión activa a la base de datos significaba que el hilo tenía un bloqueo real en la base de datos. Hoy en día, ya no existe un verdadero "bloqueo de base de datos", aunque los subprocesos pueden bloquearse si no pueden adquirir una conexión de base de datos para realizar una operación en particular.

isDbLockedByOtherThreads está en desuso desde el nivel de API 16.

mixel
fuente
No tiene sentido usar una instancia por hilo. SQLiteOpenHelper es seguro para subprocesos. Esto también es muy ineficiente para la memoria. En su lugar, una aplicación debe mantener una sola instancia de SQLiteOpenHelper por base de datos. Para una mejor simultaneidad, se recomienda utilizar WriteAheadLogging, que proporciona agrupación de conexiones para hasta 4 conexiones.
ejboy
@ejboy Lo decía en serio. "Use singleton (= uno) SQLiteOpenHelper en cada hilo", no "por hilo".
mixel
15

Respecto a las preguntas:

Mi administrador de base de datos es un singleton y ahora mismo abre una conexión a la base de datos cuando se inicializa.

Deberíamos dividir 'abrir DB', 'abrir una conexión'. SQLiteOpenHelper.getWritableDatabase () da una base de datos abierta. Pero no tenemos que controlar las conexiones como se hace internamente.

¿Es seguro dejar la base de datos abierta todo el tiempo para que cuando alguien llame a mi clase para trabajar con la base de datos ya esté abierta?

Sí lo es. Las conexiones no se cuelgan si las transacciones se cierran correctamente. Tenga en cuenta que su base de datos también se cerrará automáticamente si GC la finaliza.

¿O debo abrir y cerrar la base de datos antes y después de que se necesite cada acceso?

Cerrar la instancia de SQLiteDatabase no da nada tremendo excepto cerrar conexiones, pero esto es malo para un desarrollador si hay algunas conexiones en este momento. Además, después de SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () devolverá una nueva instancia.

¿Hay algún daño en dejarlo abierto todo el tiempo?

No, no lo hay. Tenga en cuenta también que cerrar la base de datos en un momento y subproceso no relacionados, por ejemplo, en Activity.onStop () podría cerrar las conexiones activas y dejar los datos en un estado inconsistente.

matreshkin
fuente
Gracias, después de leer sus palabras "después de SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () devolverá una nueva instancia" Me di cuenta de que por fin tengo una respuesta a un viejo problema de mi aplicación. A un problema, que se volvió crítico después de la migración a Android 9 (consulte stackoverflow.com/a/54224922/297710 )
yvolk
1

Desde la perspectiva del rendimiento, la forma óptima es mantener una única instancia de SQLiteOpenHelper en el nivel de la aplicación. La apertura de la base de datos puede ser costosa y es una operación de bloqueo, por lo que no debe realizarse en el hilo principal y / o en los métodos del ciclo de vida de la actividad.

setIdleConnectionTimeout () método (introducido en Android 8.1) se puede usar para liberar RAM cuando no se usa la base de datos. Si se establece el tiempo de espera inactivo, las conexiones de la base de datos se cerrarán después de un período de inactividad, es decir, cuando no se haya accedido a la base de datos. Las conexiones se volverán a abrir de forma transparente a la aplicación cuando se ejecute una nueva consulta.

Además de eso, una aplicación puede llamar a releaseMemory () cuando pasa a segundo plano o detecta presión de memoria, por ejemplo, en onTrimMemory ()

ejboy
fuente
0

También puede utilizar ContentProvider. Hará estas cosas por ti.

Gregory Buiko
fuente
-9

Cree su propio contexto de aplicación, luego abra y cierre la base de datos desde allí. Ese objeto también tiene un método OnTerminate () que puede usar para cerrar la conexión. No lo he probado todavía, pero parece un mejor enfoque.

@binnyb: No me gusta usar finalize () para cerrar la conexión. Podría funcionar, pero por lo que entiendo, escribir código en un método finalize () de Java es una mala idea.

Leander
fuente
No desea confiar en finalizar porque nunca sabe cuándo se llamará. Un objeto puede permanecer en el limbo hasta que el GC decida limpiarlo, y solo entonces se llamará a finalize (). Por eso es inútil. La forma correcta de hacerlo es tener un método al que se llama cuando el objeto ya no se usa (independientemente de que se haya recolectado basura o no), y eso es exactamente lo que es onTerminate ().
erwan
5
Los documentos dicen claramente que onTerminateno se llamará en un entorno de producción - developer.android.com/reference/android/app/…
Eliezer