Gleichzeitiger Datenbankzugriff
Gleicher Artikel in meinem Blog (ich formatiere gerne mehr)
Ich habe einen kleinen Artikel geschrieben, der beschreibt, wie Sie den Zugriff auf Ihren Android-Datenbank-Thread sicher machen können.
Angenommen, Sie haben Ihren eigenen SQLiteOpenHelper .
public class DatabaseHelper extends SQLiteOpenHelper { ... }
Jetzt möchten Sie Daten in separaten 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();
In Ihrem Logcat wird die folgende Meldung angezeigt, und eine Ihrer Änderungen wird nicht geschrieben.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Dies geschieht, weil jedes Mal, wenn Sie einen neuen SQLiteOpenHelper erstellen Objekt tatsächlich eine neue Datenbankverbindung herstellen. Wenn Sie versuchen, gleichzeitig von tatsächlich unterschiedlichen Verbindungen in die Datenbank zu schreiben, schlägt eine fehl. (aus der obigen Antwort)
Um eine Datenbank mit mehreren Threads zu verwenden, müssen wir sicherstellen, dass wir eine Datenbankverbindung verwenden.
Lassen Sie uns den Datenbankmanager der Singleton-Klasse erstellen, der ein einzelnes SQLiteOpenHelper- Objekt enthält und zurückgibt .
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 Daten in separaten Threads in die Datenbank schreibt, sieht folgendermaßen aus.
// 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 Ihnen einen weiteren Absturz bringen.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Da wir nur eine Datenbankverbindung, Methode verwenden getDatabase () zurückgeben dieselbe Instanz von SQLiteDatabase Objekt für Thread1 und Thread2 . Was geschieht, Thread1 Datenbank schließen kann, während Thread2 es immer noch verwendet , ist. Deshalb haben wir IllegalStateException .
Wir müssen sicherstellen, dass niemand die Datenbank verwendet, und sie erst dann schließen. Einige Leute auf stackoveflow haben empfohlen, Ihre SQLiteDatabase niemals zu schließen . Dies führt zu der folgenden Logcat-Nachricht.
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 die openDatabase () -Methode von DatabaseManager aufrufen Klasse . Innerhalb dieser Methode haben wir einen Zähler, der angibt, wie oft die Datenbank geöffnet ist. Wenn es gleich eins ist, müssen wir eine neue Datenbankverbindung erstellen. Andernfalls wird bereits eine Datenbankverbindung erstellt.
Das gleiche passiert in der Methode closeDatabase () . Jedes Mal, wenn wir diese Methode aufrufen, wird der Zähler verringert. Immer wenn er auf Null geht, wird die Datenbankverbindung geschlossen.
Jetzt sollten Sie in der Lage sein, Ihre Datenbank zu verwenden und sicherzustellen, dass sie threadsicher ist.