我们AsyncTasks
用来访问数据库表和游标.
不幸的是,我们偶尔会看到有关数据库被锁定的例外情况.
E/SQLiteOpenHelper(15963): Couldn't open iviewnews.db for writing (will try read-only): E/SQLiteOpenHelper(15963): android.database.sqlite.SQLiteException: database is locked E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.native_setLocale(Native Method) E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.setLocale(SQLiteDatabase.java:1637) E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.(SQLiteDatabase.java:1587) E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:638) E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:659) E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:652) E/SQLiteOpenHelper(15963): at android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.java:482) E/SQLiteOpenHelper(15963): at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:193) E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:98) E/SQLiteOpenHelper(15963): at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:158) E/SQLiteOpenHelper(15963): at com.iview.android.widget.IViewNewsTopStoryWidget.initData(IViewNewsTopStoryWidget.java:73) E/SQLiteOpenHelper(15963): at com.iview.android.widget.IViewNewsTopStoryWidget.updateNewsWidgets(IViewNewsTopStoryWidget.java:121) E/SQLiteOpenHelper(15963): at com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:338) E/SQLiteOpenHelper(15963): at com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:1) E/SQLiteOpenHelper(15963): at android.os.AsyncTask$2.call(AsyncTask.java:185) E/SQLiteOpenHelper(15963): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:256) E/SQLiteOpenHelper(15963): at java.util.concurrent.FutureTask.run(FutureTask.java:122) E/SQLiteOpenHelper(15963): at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:648) E/SQLiteOpenHelper(15963): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:673) E/SQLiteOpenHelper(15963): at java.lang.Thread.run(Thread.java:1060)
有没有人有一个代码的一般例子,它从一个读取的不同线程写入数据库,我们如何确保线程安全.
我有一个建议是使用a ContentProvider
,因为这将处理来自多个线程的数据库访问.我将看看这个,但这是处理这样一个问题的推荐方法吗?考虑到我们在前面或后面谈论,它似乎相当重量级.
我们最后用了一个ContentProvider
.这似乎可以解决问题.
我只是通过确保所有数据库打开都已关闭来解决同样的异常,并且(更重要的是)确保这一点,使每个数据库实例的范围仅限于需要它的方法.ContentProvider是从多个线程访问数据库时使用的一个好的,安全的类,但也要确保使用良好的数据库实践:
将db实例保持为本地(没有SQLiteDatabase类成员!)
close()
以与打开它相同的方法调用db
调用close()
你从db获得的游标
听取LogCat对SQLiteDatabse可能有的任何抱怨
在一些代码之前,让我们恢复一些方法:
信号量:目前为止提供的最佳解决方案.它是问题的核心:资源共享!它将处理数据库访问的锁定,避免冲突(database is locked
).
Java同步:一种信号量实现,但不那么软化.使用synchronized
您将无法轻松解决涉及交易的一些案例.
ContentProvider:实现ContentProvider
仅针对某些情况解决问题(或扫除地毯下的问题).你还会遇到同样的问题.区别在于ContentProvider
模式将指导您在访问Sqlite数据库时不会出现一些常见错误.该 ContentProvider的文档说:"你不需要供应商如果使用完全是你自己的应用程序中使用SQLite数据库."
几乎是强制性的:保持db实例本地,close()
使用finally
语句打开它的同一方法调用db,使用语句close()
使用游标finally
等等几乎是必须的,以避免使用Sqlite的问题.
让我们展示一个Moss提供的信号量解决方案的例子,我从CL中获取.并且适合交易.
class DataAccess { private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); public Data readSomething(int id) { Cursor c = null; r.lock(); try { c = getReadableDatabase().query(...); return c.getString(0); } finally { if (c != null) c.close(); r.unlock(); } } public void changeSomething(int id, int value) { w.lock(); try { getWritableDatabase().update(...); } finally { w.unlock(); } } private void beginTransactionWithSemaphores() { getWritableDatabase().beginTransactionWithListener(new SQLiteTransactionListener() { @Override public void onBegin() { w.lock(); } @Override public void onRollback() { w.unlock(); } @Override public void onCommit() { w.unlock(); } }); } }
考虑到SQLite数据库是基于文件的,并不打算以多进程方式访问.将SQLite与多处理混合的最佳过程是在每个与数据库相关的访问中使用信号量(aquire(),release()).
如果您创建一个获取/释放全局信号量的Db包装器,那么您的数据库访问将是线程安全的.实际上这意味着您可以获得一个bootleneck,因为您正在排队访问数据库.因此,如果它是一个改变数据库的操作,你只能用信号量包装访问,所以当你改变数据库时,没有人能够访问它并等到写过程完成.