Acceso concurrente a la base de datos
Mismo artículo en mi blog (me gusta formatear más)
Escribí un pequeño artículo que describe cómo hacer que el acceso a su hilo de base de datos de Android sea seguro.
Suponiendo que tiene su propio SQLiteOpenHelper .
public class DatabaseHelper extends SQLiteOpenHelper { ... }
Ahora desea escribir datos en la base de datos en hilos separados.
// Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
Recibirá el siguiente mensaje en su logcat y uno de sus cambios no se escribirá.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Esto sucede porque cada vez que crea un nuevo objeto SQLiteOpenHelper , en realidad está haciendo una nueva conexión de base de datos. Si intenta escribir en la base de datos desde conexiones distintas reales al mismo tiempo, una fallará. (de la respuesta anterior)
Para usar la base de datos con múltiples hilos necesitamos asegurarnos de que estamos usando una conexión de base de datos.
Hagamos el Administrador de base de datos de clase singleton que contendrá y devolverá un solo objeto SQLiteOpenHelper .
public class DatabaseManager {
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initialize(..) method first.");
}
return instance;
}
public SQLiteDatabase getDatabase() {
return new mDatabaseHelper.getWritableDatabase();
}
}
El código actualizado que escribe datos en la base de datos en hilos separados se verá así.
// In your application class
DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
// Thread 1
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
// Thread 2
DatabaseManager manager = DatabaseManager.getInstance();
SQLiteDatabase database = manager.getDatabase()
database.insert(…);
database.close();
Esto te traerá otro choque.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Puesto que estamos utilizando sólo una conexión de base de datos, método getDatabase () devolver misma instancia de SQLiteDatabase objeto para Thread1 y Thread2 . Lo que está sucediendo, Thread1 puede cerrar la base de datos, mientras que Thread2 todavía la está usando. Es por eso que tenemos el bloqueo IllegalStateException .
Necesitamos asegurarnos de que nadie esté usando la base de datos y solo luego cerrarla. Algunas personas en stackoveflow recomendaron nunca cerrar su SQLiteDatabase . Esto dará como resultado el siguiente mensaje logcat.
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
Muestra de trabajo
public class DatabaseManager {
private int mOpenCounter;
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase openDatabase() {
mOpenCounter++;
if(mOpenCounter == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
mOpenCounter--;
if(mOpenCounter == 0) {
// Closing database
mDatabase.close();
}
}
}
Úselo de la siguiente manera.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
Cada vez que necesite una base de datos, debe llamar al método openDatabase () de la clase DatabaseManager . Dentro de este método, tenemos un contador que indica cuántas veces se abre la base de datos. Si es igual a uno, significa que necesitamos crear una nueva conexión de base de datos, de lo contrario, la conexión de base de datos ya está creada.
Lo mismo sucede en el método closeDatabase () . Cada vez que llamamos a este método, el contador disminuye, cada vez que llega a cero, estamos cerrando la conexión de la base de datos.
Ahora debería poder usar su base de datos y asegurarse de que sea segura para subprocesos.