Accesso simultaneo al database
Stesso articolo sul mio blog (mi piace più la formattazione)
Ho scritto un piccolo articolo che descrive come rendere sicuro l'accesso al thread del database Android.
Supponendo che tu abbia il tuo SQLiteOpenHelper .
public class DatabaseHelper extends SQLiteOpenHelper { ... }
Ora vuoi scrivere i dati nel database in thread separati.
// 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();
Verrà visualizzato il seguente messaggio nel logcat e una delle modifiche non verrà scritta.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Questo accade perché ogni volta che crei un nuovo oggetto SQLiteOpenHelper stai effettivamente realizzando una nuova connessione al database. Se si tenta di scrivere nel database da connessioni distinte effettive contemporaneamente, si verificherà un errore. (dalla risposta sopra)
Per utilizzare il database con più thread, dobbiamo assicurarci di utilizzare una connessione al database.
Facciamo Database Manager di classe singleton che conterrà e restituirà un singolo oggetto 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();
}
}
Il codice aggiornato che scrive i dati nel database in thread separati sarà simile al seguente.
// 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();
Questo ti porterà un altro incidente.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Poiché stiamo utilizzando una sola connessione al database, metodo getDatabase () restituire stessa istanza SQLiteDatabase oggetto per Filettatura1 e Thread2 . Quello che sta succedendo, Thread1 potrebbe chiudere il database, mentre Thread2 lo sta ancora usando. Ecco perché abbiamo crash IllegalStateException .
Dobbiamo assicurarci che nessuno stia usando il database e solo allora lo chiuda. Alcune persone su Stackoveflow consigliano di non chiudere mai il database SQLite . Ciò comporterà il seguente messaggio logcat.
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
Campione di lavoro
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();
}
}
}
Usalo come segue.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
Ogni volta che è necessario il database, è necessario chiamare il metodo openDatabase () della classe DatabaseManager . All'interno di questo metodo, abbiamo un contatore, che indica quante volte viene aperto il database. Se è uguale a uno, significa che è necessario creare una nuova connessione al database, in caso contrario, la connessione al database è già stata creata.
Lo stesso accade nel metodo closeDatabase () . Ogni volta che chiamiamo questo metodo, il contatore viene ridotto, ogni volta che va a zero, chiudiamo la connessione al database.
Ora dovresti essere in grado di utilizzare il tuo database ed essere sicuro che sia thread-safe.