android重定义cursor,微信移动数据库组件WCDB(四) — Android 特性篇

微信的移动端数据库组件 WCDB 已经正式开源了,有关注的小伙伴可能已经用上了。如果还没用上,可以翻到文末关注我们的 GitHub 和公众号其他文章。

之前我们已经发过几篇 iOS 和修复的文章,Android 由于接口跟系统几乎一样,相信大家都比较熟悉,不熟悉用法也可以到 Android Developer 官网看一下。但是,我们也有一些特色功能和优化大家可能不容易注意到, 现在就单独拿出来说说。

加密接口

WCDB 使用了 SQLCipher 的 C 层库,但没有直接使用 SQLCipher Android 的封装层。SQLCipher Android 封装层中很多设置需要手写 PRAGMA 语句实现,比如设置 KDF 迭代次数(兼容老版本 SQLCipher DB)、设置 Page Size 等操作。

a3f206b3afe26287e4dfa2b03827efc1.png

对于开发者来说,这需要了解 SQLCipher 底下的 PRAGMA 指令,更重要的是要搞清楚这些指令正确的调用顺序。 哪些是需要在设置 key 之前执行的?哪些是只有设置了 key 之后才生效的?开发者往往必须仔细查阅 SQLCipher 的文档来了解这些细节。

WCDB 对这个部分做了改进,封装了SQLiteCipherSpec用于设置加密参数,设置好了传给SQLiteDatabase工厂方法就好了,不需要考虑 PRAGMA 语法和调用顺序。

4116d560ff9812d3d6465fa969af1dbd.png

使用SQLiteCipherSpec另一个好处是,同样的结构可以传给RepairKit用于恢复损坏 DB,不需要两套 接口了。由于 RepairKit 底层不使用 PRAGMA,原来 hook 的形式不能满足需要。

另外,WCDB 将String类型的密码改为byte[]类型,可以支持非打印字符作为密码(比如hash(user id)方式),原来字符类型密码只要转换为 UTF-8 的 byte 数组即可,和 SQLCipher Android 兼容。

数据迁移

SQLCipher 提供了sqlcipher_exportSQL 函数用于导出数据到挂载的另一个 DB,可以用于数据迁移。 但这个函数用于 Android 的SQLiteOpenHelper并不方便。

SQLiteOpenHelper主要帮助开发者做 Schema 版本管理,通过它打开 SQLite 数据库,会读取user_version字段来判断是否需要升级,并调用子类实现的onCreate、onUpgrade等接口来完成创建或升级操作。sqlcipher_export由于是导出而非导入,就跟onCreate等接口不搭了,因为要关闭原来的 DB, 打开老的 DB,执行 export 到新 DB,再重打开。

为了方便使用,WCDB 就做了扩展,将sqlcipher_export扩展为可以接受第二个参数表示从哪里导出, 从而实现了导入。

874291c75403c27b236d1d64ca90b11d.png

如此就可以不关闭原来的数据库实现数据导入,可以兼容SQLiteOpenHelper的接口了。详细可以看我们的 Sample。

全文搜索分词器与动态 ICU 加载

WCDB Android 自带了一个 FTS3/4 分词器,名为mmicu,用于实现SQLite 全文搜索。 分词器的使用与 SQLite 自带的simple、icu等分词器一样,创建虚拟表的时候带上名字即可:

212c74d9de66fb3111f86030145b62bb.png

MMICU 分词器与官方 ICU 分词器类似,但对中文(象形文字)分词以及 ICU 库加载做了特殊处理。 ICU 对中文的分词是基于词库的,Android 系统不同版本会附带不同版本的 ICU,捎带不同版本的中文 词库,当然也会带来不同的分词结果,这个对于统一产品体验是非常不利的。

另外,ICU 自带的中文词库并非非常完整,组词效果也一般,但若自带一个完整好用的词库, 又需要非常大的空间,这个空间会体现在 APK 体积上。最终,我们做了折中, 中文字全部单字成词,其他文字则使用 ICU 默认规则。

ICU 还有一个严重的问题是动态库和自带的数据文件体积很大,超过 10MB,编译进 APK 里相当不划算, 最好能直接加载系统自带的 ICU 库。但加载系统库有另一个障碍:ICU 库不同版本会在函数名称后面 带上版本号后缀,直接编译时连接行不通。

为了克服这个障碍,WCDB 做了一个兼容层icucompat,通过系统带的数据文件推断 ICU 版本, 通过dlopen动态加载不同的符号名称,然后通过宏来模拟直接调用方便开发。最终实现效果便是 在不需要自带 ICU 库的前提下使用 ICU 库的断词、归一化等功能,为最终 APK 包省下 10MB 以上空间。

有了 ICU 兼容层,要实现 Android 框架自带的 ICU 相关功能就简单了,比如LOCALIZED排序。 但是,WCDB 目前没有接入(主要是没有相关需求),有这方面需要的话,可以到我们的 GitHub 提 Issue 哦。

日志重定向与性能监控

SQLite 和 WCDB 框架在运行中会产生日志,这些日志默认会打印到系统日志(logcat),但这可能不是 所有开发者都希望的行为。比如担心日志里带有敏感信息,直接输出到系统不妥,或者希望将日志写到文件 用于上报和分析,WCDB 提供接口来完成日志重定向。

e67ac3aefc0efb5046e37a72861e52fe.png

要实现高性能日志持久化,可以考虑使用我们 mars 里面的 xlog 组件。

WCDB 还提供了性能监控接口SQLiteTrace,实现接口并绑定到SQLiteDatabase可以在每次 执行 SQL 语句或连接池拥堵的时候得到回调。

bb9d9905b6bf3af0bb764be7fc1983b8.png

SQLiteDatabase也开放了dump方法,可以打印出数据库的当前状态,包括连接池内所有连接 被持有的状态以及最近执行的 SQL 语句和耗时,对排查性能和死锁问题也有很大帮助。

优化 Cursor 实现

在 WCDB 发布时,我们的一篇文章上提到 Cursor 实现优化。对于 查询获取 Cursor → 遍历 → 关闭 这种简单的场景,我们通过SQLiteDirectCursor直接操作 SQLite 底层的查询,避免 CursorWindow 的重复分配带来的损耗。

需要注意的是 Direct Cursor 未关闭前会占用一个数据库连接,使用完需要尽快关闭,否则会一直占用 造成别的线程无法请求到连接。遍历 Cursor 过程中同一线程不做其他 DB 操作,遍历完关闭,配合 WAL 使用,是最佳实践。

关注我们

欢迎到 GitHub 关注 WCDB 的最新动态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值