WCDB是腾讯微信团队开源的客户端数据库框架,拥有高性能和支持加密等重要特性,并且可用于Android、iOS、Windows、macOS等多个平台。 我们知道,原生的加密数据库框架SQLCipher和不加密的SQLite相比,性能差距还是很大的,加密会使得读写效率严重下降,而WCDB很好地兼顾了性能和安全问题。
对使用了Google官方Jetpack Room库的开发者,WCDB 1.x版本也提供了完美支持:Use WCDB with Room ,然而,WCDB 2.x版本后变成了一个纯ORM框架,虽然也支持Java、Kotlin等语言(本质上就是一层封装,底层接口都一样),但是暂时没有计划支持兼容Room,从官方文档看,2.x版本更纯粹,一套代码跨全平台,所以也不关注各平台的其他框架兼容了。
跟WCDB的开发者也聊了聊,他们团队对Room的支持兴趣不大:issues#1052 。其实浏览一下2.x版本的更新日志,看似开发团队是iOS研发主导,很多更新也和Android无关。
不过问题不大,1.x版本的性能和稳定性已经非常好,连微信客户端自己都用了好多年。唯一的一个小问题是,Room最新版本支持了 @Upsert 注解,如果还继续使用 com.tencent.wcdb:room:1.x
库的话,在插入数据时没问题,但在更新数据时会崩溃,抛出异常:SQLiteConstraintException
。
解决方案有两种:
- 摆烂,不再使用Room的Upsert注解,把Upsert拆成Insert和Update,这样需要改动很多现有代码。
- Read the fucking code,看看为什么崩溃。
如果你选择第一种方案,后面就不用看了,但是为什么不看看第二种方案呢?/狗头
其实Upsert的实现很简单,最终执行插入或更新操作的源码在 EntityUpsertAdapter :
/**
* Upserts the given entities into the database and returns the row ids.
*
* @param entities Entities to upsert
* @return The SQLite row ids, for entities that are not inserted the row id returned will be -1
*/
fun upsertAndReturnIdsArray(entities: Array<out T>): LongArray {
return LongArray(entities.size) { index ->
try {
insertionAdapter.insertAndReturnId(entities[index])
} catch (ex: SQLiteConstraintException) {
checkUniquenessException(ex)
updateAdapter.handle(entities[index])
-1
}
}
}
可以看出,每次Upsert操作都会先进行插入,如果数据已经存在,会抛异常,在catch捕获异常后,再进行更新数据的操作。所以理论上Room是可以捕获异常的,为什么集成WCDB之后就捕获不到了呢?
原因是WCDB对SQLite的很多类都重新进行了封装,一些基本的代码虽然没有改动,但是包名却变成了 com.tencent.wcdb.database
:
package com.tencent.wcdb.database;
/**
* An exception that indicates that an integrity constraint was violated.
*/
@SuppressWarnings("serial")
public class SQLiteConstraintException extends SQLiteException {
public SQLiteConstraintException() {}
public SQLiteConstraintException(String error) {
super(error);
}
}
而Room只能捕获原生的 android.database.sqlite
包下的异常。知道原因后,这就好改了,只需要改造 WCDBStatement
即可:
import com.tencent.wcdb.database.SQLiteConstraintException;
// ...
class WCDBStatement implements SupportSQLiteStatement {
// ...
@Override
public long executeInsert() {
try {
return mDelegate.executeInsert();
} catch (SQLiteConstraintException ex) {
// 兼容新版Room的Upsert注解
throw new android.database.sqlite.SQLiteConstraintException(ex.getMessage());
}
}
}
捕获WCDB自定义的异常后,转换一次,抛出原生SQLite库的异常,这样就能被Room的EntityUpsertAdapter捕获到,顺利兼容Upsert操作。
当然, 我已经把这些兼容处理好了,并且迁移了相关的依赖库为AndroidX,同时集成最新版本的SQLCipher,才有了:WCDBRoomX
可以删掉所有WCDB、SQLCipher、SQLite相关的依赖,直接引入WCDBRoomX即可:
dependencies {
// implementation("androidx.sqlite:sqlite-ktx:2.4.0")
// implementation("net.zetetic:sqlcipher-android:4.5.6@aar")
// implementation("com.tencent.wcdb:wcdb-android:1.1-19")
implementation("com.github.ysy950803:WCDBRoomX:1.0.0")
// 当然,Room还是要保留的,方便独立更新,WCDBRoomX为了轻便,不会包含Room
val roomVersion = "2.6.1"
kapt("androidx.room:room-compiler:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
}
快速集成:
Room.databaseBuilder(...)
.openHelperFactory(WCDBRoomX.createOpenHelperFactory("db_password"))
.build()
如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )