Concurrent database access
SQLite is a common practice for Android Dev, and it makes us wonder if SQLite is thread-safe compared to the console explosion you’ve probably experienced.
OK nonsense not much say, we ⬇️
Just start
First, suppose you have implemented an SQLiteHelper class like this:
public class DatabaseHelper extends SQLiteOpenHelper {... }Copy the code
Now you want to write some data to SQLite in two child threads:
// Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = newDatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); The database insert (...). ; database.close();// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = newDatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); The database insert (...). ; database.close();Copy the code
Isn’t it? It looks fine. Nothing wrong with it.
In this case, we click run, gio, and you will receive the following gift “error” in your logcat:
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Copy the code
What’s going on?
We analyzed the error and found that it was caused by a link to the database every time you created SQLiteHelper. At this point, if you try to write to the database from actually different links at the same time, you’re bound to fail.
To sum up, if we want to do anything with the database, including reading and writing, on different threads, we have to make sure that we’re using the same connection
Ok, so now the question is clear. Now let’s create a singleton schema class: DatabaseManager to create and return unique, singleton DatabaseManager objects.
Ps Some students asked me what singletons are, so I wrote this blog post to explain singletons – globally available context objects. Is that enough
public class DatabaseManager {
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = newDatabaseManager(); mDatabaseHelper = helper; }}public static synchronized DatabaseManager getInstance(a) {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initialize(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase getDatabase(a) {
returnmDatabaseHelper.getWritableDatabase(); }}Copy the code
Now, let’s go back and modify the previous code. The result is as follows:
// In your application class
DatabaseManager.initializeInstance(new DatabaseHelper());
// Thread 1DatabaseManager manager = DatabaseManager.getInstance(); SQLiteDatabase database = manager.getDatabase() database.insert(...) ; database.close();// Thread 2DatabaseManager manager = DatabaseManager.getInstance(); SQLiteDatabase database = manager.getDatabase() database.insert(...) ; database.close();Copy the code
The logic is clearer and there is less code redundancy. Now that we’re running down the code, we get another cache:
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Copy the code
Don’t panic, let’s take a closer look at the error, and we find: The singleton ensures that only one SQLiteHelper object is available for Thread 1 and Thread 2, but the problem is that when we finish running Thread 1, our database.close(); The connection to the database has been closed for us, but in the meantime our Thread 2, “Thread 2,” still keeps the reference to SQLiteHelper. For this reason, we received an IllegalStateException.
So we need to make sure that SQLiteHelper is disconnected when no one else is using it.
Make sure SQLIiteHelper is disconnected only when no one is using it
The solution to this problem has been advised by many stackoveFlow people: never disconnect SQLiteHelper, but instead you get the following output on logcat:
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
Copy the code
So, I strongly advise against using this method. To solve this problem, we introduce the concept of a counter
The standard sample
You will have a counter that perfectly solves the problem of opening/closing a database connection:
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = newDatabaseManager(); mDatabaseHelper = helper; }}public static synchronized DatabaseManager getInstance(a) {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase openDatabase(a) {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase(a) {
if(mOpenCounter.decrementAndGet() == 0) {
// Closing databasemDatabase.close(); }}}Copy the code
We can use it in threads like this:
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase(); database.insert(...) ;// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
Copy the code
Whenever you need to use a database, you simply call the openDatabase() method in DatabaseManager. In this method, we have a mOpenCounter object that records how many times the database has been “opened”. When it is equal to 1, it means that you need to create a new database connection to use the database, otherwise, the database is already in use.
The same happens with the closeDatabase() method, where our mOpenCounter object is decrement by one each time you call it. When it drops to zero, we close the connection to the database.
Perfect, and finally:
- Now you can use your database however you want, and you can trust it to be thread-safe!
- Of course, many students on the use of the database, there are a lot of doubts, I will later for the use of the database, make a series of summaries, interested in _Yuanhao can continue to pay attention to the programming world
Related articles
Everyone has to learn picture compression to effectively solve the Android app OOM
Android gives your Room a free ride on the RxJava bandwagon from repetitive code
The founder of the ViewModel and ViewModelProvider. Factory: the ViewModel
Singleton pattern – globally available Context objects, enough for this article
Scale gesture ScaleGestureDetector source code analysis, this article is enough
ObjectAnimator, ValueAnimator, ObjectAnimator, ValueAnimator
After watching this animation frame of Never View again, I kneeled and rubbed the clothes board
Android custom clock control hour, minute hand, second hand drawing this article is enough
Android custom control – draw clock dial
Android Advanced custom ViewGroup Custom layout
Welcome to attention_yuanhaoThe nuggets!
Regularly share Android development wet goods, the pursuit of humor and depth of the article perfect unity.
About the source Demo link: in order to write Demo spent several days, I hope you order songs star~ thank you!