SQLite多线程读写实践及常见问题总结
发布时间:2020-12-13 00:01:26 所属栏目:百科 来源:网络整理
导读:基本操作的部分,大家都很熟悉了,这里根据个人切身经验,总结了一些经常遇到的,也需要注意的一些问题,与大家分享,水平有限,不妥或者错误的地方还望指出。 多线程读写 SQLite 实质上是将数据写入一个文件,通常情况下,在应用的包名下面都能找到 xxx.db
基本操作的部分,大家都很熟悉了,这里根据个人切身经验,总结了一些经常遇到的,也需要注意的一些问题,与大家分享,水平有限,不妥或者错误的地方还望指出。
我们可以得知 SQLite 是文件级别的锁:多个线程可以同时读,但是同时只能有一个线程写。 Android 提供了 SqliteOpenHelper 类,加入 Java 的锁机制以便调用。 如果多线程同时读写(这里的指不同的线程用使用的是不同的 Helper 实例),后面的就会遇到 android.database.sqlite.SQLiteException: database is locked 这样的异常。 对于这样的问题,解决的办法就是 keep single sqlite connection , 保持单个 SqliteOpenHelper 实例,同时对所有数据库操作的方法添加 synchronized 关键字。 如下所示: 复制内容到剪贴板 代码:public class DatabaseHelper extends SQLiteOpenHelper {
Android为我们提供了SqliteOpenHelper类,我们可以通过getWritableDatabase 或者 getReadableDatabase 拿到 SQLiteDatabase 对象,然后执行相关方法。这 2 个方法名称容易给人误解,我也在很长的一段时间内想当然的认为 getReadabeDatabase 就是获取一个只读的数据库,可以获取很多次,多个线程同时读,用完就关闭,实际上 getReadableDatabase 先以读写方式打开数据库,如果数据库的磁盘空 间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。 代码: public synchronized SQLiteDatabase getReadableDatabase() {
在多线程中,如果第一个线程先调用getWritableDatabase,后面线程再次调用,或者第一个线程先调用getReadableDatabase,后面的线程调用getWritableDatabase,那么后面的这个方法是会失败的,因为数据库文件打开后会加锁,必须等前面的关闭后后面的调用才能正常执行,正是因为这个原因,可以1 Write+Many Read(有可能产生冲突,因为第一个getReadableDatabase有可能先于getWritableDatabase执行,导致后面的失败),也可以Many Read,但是不可能Many Write。所以使用单例加上同步的数据库操作方法,就不会出现死锁的问题,这部分例子请参照附件,多线程可以运行的很好,另外关于Sqlite database locking collisions example,网上有很不错的一个例子,可以 这里 去下载。 其实我觉得理论上可以修改getReadableDatabase方法,打开的数据库都是Read Only的,这样就能同时1 Write+Many Read,只不过要保证打开之前,数据库要创建或者升级好,这样读操作就不会互斥写操作,效率相对更高。 关于数据库关闭的问题,在下面好的习惯中会专 门说明。 代码: db.beginTransaction();
使用事务对于批量更新有极大的好处,因为单次更新会频繁的调用数据库,曾经我同步过联系人,没使用事务之前, 300 个联系人写入自己的数据库大概需要 3~5 秒钟的时间,引入事务后,读取联系人的时间没有减少,但是所有更新的时间降为 200ms 级,提升极为明显。 实际上多次数据库变动的升级是很痛苦的事情,要考虑每一个旧的版本,理论上用户可以从任何一个旧的版本直接升级到最新版本,我们需要考虑每一种情况。 在 onUpgrade 方法中,针对每一种版本号,先把旧的临时数据保存下来,删去旧的表,创建新表,然后将数据根据情况插入到新表中,不需要的字段可以丢弃,新增字段填默认值,数据可以临时存放到一个数组中,或者可以临时 cache 到文件中,最后将临时文件清空。 更新操作可以使用事务提高效率,另外需要知道的是 I/O 操作时耗时的,如果数据量较大,还需要放到单独的线程中处理,防止阻塞 UI 。 解决这个问题有 4 个方法: 1.改名称(最简单): aapt 工具在打包 apk 文件时,会将资源文件压缩以减小安装包大小( raw 文件夹下的资源则不受影响)。 但是可以通过修改文件成下面的扩展名,逃避检查。 代码: /* these formats are already compressed,or don't compress well */
2.压缩: 如果原文件能压缩到 1M 一下,可以先压缩成 zip 或者 rar 格式,然后解压将数据库文件释放到相应位置。 3.分割文件: 大的数据,分割成多个小数据文件, info1.dat,info2.dat… ,分别读取这些文件数据插入数据库。 4.网络: 上面的几种方法都是将初始化数据放在安装包中,这样无疑会增加安装包大小,如果必要情况下,可以将数据放到服务器上,创建数据库后,通过HTTP请求,获取JSON,XML数据或者数据库文件,然后经过处理入库。 Cursor 如果不关闭,虽然不会导致出错,但是 Log 中会有错误提示,还是严谨点, Activity 中有 startManagingCursor 的方法, Activity 会在生命周期结束时关闭这些 Cursor ,其他地方,我们则需要用完关闭,以前需要 Cursor 的 Adapter 则需要在 changeCursor 时判断关闭 old cursor ,在 Activity 的 onDestory 方法中关闭 cursor 。 2.关闭DatabaseHelper 在上述单例 Helper 例子中,其实一直没有关闭数据库,但是我们阅读 getReadabeDatabase 和 getWritableDatabas 的方法,他们会关闭 Old SQLiteDatabase 的,我们只需要在 Application 的 onTerminal 方法中关闭即可,这样也能避免多线程中,一个线程关闭了数据库,导致其他线程使用的时候失败的问题。 实质上,数据库是一个文件引用,单例模式下,不关闭也不会出现问题,让它保持随单例的生命周期关闭就好了。 3.在循环外面获取 ColumnIndex ,如果表中列不是很多,每次查询又返回所有列的话,可以将列的 index 定义到 TABLE_COLUMNS 中去,这样每次获取指定列数据的话,就不用去查找 index 了。 4.数据库存放的数据类型 Android 提供了多种数据存储的方法,文件,数据库, SharePreference ,网络等,要根据情况选择合适的方式,不要把什么东西都往数据库中塞。 下面的几种情况就不适合放到数据库中: 1) 图片等二进制数据:如果是图片的话,可以将文件名称或者路径保存到数据库中,真正的文件可以作为缓存文件保存在文件系统中。 2) 临时数据:定位获取到的 Location ,登录的 Session等。 3)日志数据:可以写入文件中,通常是log_xxxx.txt。
转自:http://bbs.51cto.com/thread-990260-1.html (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |