对于 Android Dev 而言,有关 SQLite 的操做再常常不过了,相比你必定经历过控制台一片爆红的状况,这不由让咱们疑问:SQLite 究竟是线程安全的吗?java
OK 废话很少说,咱们 ⬇️android
public class DatabaseHelper extends SQLiteOpenHelper { ... }
复制代码
如今你想要在两个子线程中,分别地向 SQLite 里写入一些数据:git
// 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();
复制代码
对吧?看上去很 OK 没啥毛病。github
那么这时,咱们点一下 run
,gio~ 你将会在你的 logcat 里收到以下礼物「报错」:sql
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5) 复制代码
咱们分析一下报错终于发现:这是因为你每次建立 SQLiteHelper 时,都对数据库进行了一个连接操做。这时,若是你尝试着,同时从实际不一样的连接中,对数据库进行写入操做,失败就是必然的了。数据库
总结一下 若是咱们想再不一样的线程中,对数据库进行包括读写操做在内的任何使用,咱们就必须得确保,咱们使用的是同一个的链接编程
好,那如今问题就明了了。如今让咱们建立一个单例模式类:DatabaseManager
用来建立和返回惟一的,单例 DatabaseManager
对象。安全
ps 有些同窗问我什么是单例模式,我专门跑去写了这篇博客来解释下,单例模式-全局可用的 context 对象,这一篇就够了app
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 synchronized SQLiteDatabase getDatabase() {
return mDatabaseHelper.getWritableDatabase();
}
}
复制代码
如今,咱们在回来修改下以前的代码,结果以下所示:框架
// In your application class
DatabaseManager.initializeInstance(new DatabaseHelper());
// 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();
复制代码
逻辑比以前更清晰,代码冗余也少了。如今咱们在跑下代码,这时咱们会收到,另外一个 cache
:
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
复制代码
不要慌,咱们仔细分析下报错,咱们发现:单例模式的使用保证了咱们,在线程1、二「Thread 一、Thread 2 中」只会得到到惟一的 SQLiteHelper 对象,但这时问题就来了,当咱们运行完线程一「Thread 1」时,咱们的 database.close();
已经替咱们关闭了对数据库的链接,但与此同时咱们的线程二「Thread 2」依然保持这对 SQLiteHelper 的引用。正是这个缘由,咱们收到了IllegalStateException
的报错。
因此,这时咱们就须要保证,当没有人使用 SQLiteHelper 时,再将其断开链接。
关于这个问题的解决 stackoveflow 上不少人建议咱们:永远不要断开 SQLiteHelper 的链接,可是这样以来你会在 logcat 上获得以下输出:
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
复制代码
因此,我很是不建议你用这个方法。为了解决这个问题,咱们引入计数器的概念
经过以下方法,你将经过一个计数器来完美解决 打开/关闭 数据库链接的问题:
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 = 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() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
// Closing database
mDatabase.close();
}
}
}
复制代码
咱们在线程中能够这样使用它:
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
复制代码
每当你须要使用数据库时,你只要调用 DatabaseManager 中的 openDatabase() 方法。在这个方法中,咱们有一个,用来记录数据库被“打开”了几回的 mOpenCounter 对象。当它等于 1 时,这意味着你须要去建立新的数据库链接来使用数据库,不然的话,就说明数据库已经在使用中了。
一样的状况也发生在 closeDatabase() 方法中,当你每次调用该方法时,咱们的 mOpenCounter 对象就会减一。当它减到 0 时,咱们就去关闭这个数据库的链接。
完美,最后:
每一个人都要学的图片压缩,有效解决 Android 程序 OOM
Android 让你的 Room 搭上 RxJava 的顺风车 从重复的代码中解脱出来
ViewModel 和 ViewModelProvider.Factory:ViewModel 的建立者
单例模式-全局可用的 context 对象,这一篇就够了
缩放手势 ScaleGestureDetector 源码解析,这一篇就够了
Android 属性动画框架 ObjectAnimator、ValueAnimator ,这一篇就够了
看完这篇再不会 View 的动画框架,我跪搓衣板
Android 自定义时钟控件 时针、分针、秒针的绘制这一篇就够了
android 自定义控件之-绘制钟表盘
Android 进阶自定义 ViewGroup 自定义布局
按期分享Android开发
湿货,追求文章幽默与深度
的完美统一。
关于源码 Demo 连接:为了写 Demo 花了好几天时间,但愿你们点歌 star~ 谢谢!