Gleichzeitiger Datenbankzugriff
Derselbe Artikel in meinem Blog (ich mag die Formatierung mehr)
Ich habe einen kleinen Artikel geschrieben, der beschreibt, wie man den Zugriff auf die Android-Datenbank thread-sicher macht.
Vorausgesetzt, Sie haben Ihr eigenes SQLiteOpenHelper .
public class DatabaseHelper extends SQLiteOpenHelper { ... }
Jetzt wollen Sie die Daten in getrennten Threads in die Datenbank schreiben.
// 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();
Sie werden folgende Meldung in Ihrem Logcat erhalten und eine Ihrer Änderungen wird nicht geschrieben.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Dies geschieht, weil jedes Mal, wenn Sie eine neue SQLiteOpenHelper Objekt stellen Sie tatsächlich eine neue Datenbankverbindung her. Wenn Sie versuchen, über verschiedene Verbindungen gleichzeitig in die Datenbank zu schreiben, wird eine Verbindung fehlschlagen. (aus der obigen Antwort)
Um eine Datenbank mit mehreren Threads zu verwenden, müssen wir sicherstellen, dass wir eine Datenbankverbindung verwenden.
Erstellen wir eine Singleton-Klasse Datenbank-Manager die eine einzige Datei enthält und zurückgibt SQLiteOpenHelper objeto.
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();
}
}
Der aktualisierte Code, der die Daten in separaten Threads in die Datenbank schreibt, wird wie folgt aussehen.
// 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();
Dies wird zu einem weiteren Absturz führen.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Da wir nur eine Datenbankverbindung verwenden, ist die Methode getDatabase() geben dieselbe Instanz von SQLiteDatabase Objekt für Thema1 y Thema2 . Was geschieht? Thema1 kann die Datenbank schließen, während Thema2 immer noch verwendet. Deshalb haben wir IllegalStateException Absturz.
Wir müssen sicherstellen, dass niemand die Datenbank benutzt und sie erst dann schließen. Einige Leute auf Stackoveflow haben empfohlen, die Datenbank nie zu schließen. SQLiteDatabase . Dies führt zu der folgenden logcat-Meldung.
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
Arbeitsprobe
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();
}
}
}
Verwenden Sie es wie folgt.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
Jedes Mal, wenn Sie eine Datenbank benötigen, sollten Sie anrufen openDatabase() Methode der DatabaseManager Klasse. Innerhalb dieser Methode haben wir einen Zähler, der anzeigt, wie oft die Datenbank geöffnet wurde. Wenn er gleich eins ist, bedeutet dies, dass wir eine neue Datenbankverbindung erstellen müssen, wenn nicht, ist die Datenbankverbindung bereits erstellt.
Das Gleiche geschieht in closeDatabase() Methode. Jedes Mal, wenn wir diese Methode aufrufen, wird der Zähler verringert, und wenn er auf Null geht, wird die Datenbankverbindung geschlossen.
Jetzt sollten Sie in der Lage sein, Ihre Datenbank zu verwenden und sicher sein, dass sie thread-sicher ist.