Android数据缓存框架 - 切换为Room数据库

之前我们讲过使用内置的ORM框架进行数据的缓存,现在问题来了,有的同学项目已经使用了官方推荐的Room数据库,那怎么办呢?安排。

我们知道,数据缓存有个非常重要的角色是Repository,比如内置的就用的DoraDatabaseCacheRepository。我们现在来定义一个Room的。
```
package dora.cache.repository

import android.content.Context
import androidx.room.RoomDatabase
import dora.cache.holder.*

abstract class RoomDatabaseCacheRepository<T, D : RoomDatabase>(context: Context)
    : BaseDatabaseCacheRepository<T>(context) {

    override fun createCacheHolder(clazz: Class<T>): CacheHolder<T> {
        return RoomCacheHolder<T>(getRoomDatabase(), getDaoName(), clazz)
    }

    override fun createListCacheHolder(clazz: Class<T>): CacheHolder<MutableList<T>> {
        return RoomListCacheHolder<T>(getRoomDatabase(), getDaoName(), clazz)
    }

    abstract fun getRoomDatabase() : D

    /**
     * 返回RoomDatabase的getDao的方法名。
     */
    abstract fun getDaoName() : String
}
```
同样的,我们要把两个CacheHolder也定义一下啦。
```
package dora.cache.holder

import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import dora.cache.holder.dao.IRoomDao
import dora.db.OrmLog
import dora.db.builder.Condition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

class RoomCacheHolder<M>(var db: RoomDatabase, var daoName: String,
                         var clazz: Class<M>) : CacheHolder<M> {

    private lateinit var dao: IRoomDao<M>

    override fun init() {
        dao = db.javaClass.getDeclaredMethod(daoName).invoke(db) as IRoomDao<M>
    }

    override fun queryCache(condition: Condition): M? {
        return runBlocking {
            withContext(Dispatchers.IO) {
                val query = SupportSQLiteQueryBuilder.builder(clazz.simpleName)
                .selection(condition.selection, condition.selectionArgs)
                .groupBy(condition.groupBy)
                .orderBy(condition.orderBy)
                .limit(condition.limit)
                .having(condition.having).create()
                dao.select(query)
            }
        }
    }

    override fun removeOldCache(condition: Condition) {
        runBlocking {
            withContext(Dispatchers.IO) {
                val model = queryCache(condition)
                model?.let {
                    val ok = dao.delete(it) > 0
                    OrmLog.d("removeOldCache:$ok")
                }
            }
        }
    }

    override fun addNewCache(model: M) {
        runBlocking {
            withContext(Dispatchers.IO) {
                val ok = dao.insert(model) > 0
                OrmLog.d("addNewCache:$ok")
            }
        }
    }

    override fun queryCacheSize(condition: Condition): Long {
        val query = RoomSQLiteQuery.acquire("SELECT * FROM " + clazz.simpleName + " WHERE "
                + condition.selection, condition.selectionArgs.size)
        val cursor = runBlocking {
            withContext(Dispatchers.IO) {
                db.query(query)
            }
        }
        if (cursor != null) {
            return try {
                if (cursor.moveToFirst()) {
                    cursor.getInt(0).toLong()
                } else 0
            } finally {
                cursor.close()
                query.release()
            }
        }
        return 0
    }
}
```
```
package dora.cache.holder

import androidx.room.RoomDatabase
import androidx.room.RoomSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import dora.cache.holder.dao.IListRoomDao
import dora.db.OrmLog
import dora.db.builder.Condition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

class RoomListCacheHolder<M>(var db: RoomDatabase, var daoName: String,
                         var clazz: Class<M>) : ListCacheHolder<M>() {

    private lateinit var dao: IListRoomDao<M>

    override fun init() {
        dao = db.javaClass.getDeclaredMethod(daoName).invoke(db) as IListRoomDao<M>
    }

    override fun queryCache(condition: Condition): MutableList<M>? {
        return runBlocking {
            withContext(Dispatchers.IO) {
                val query = SupportSQLiteQueryBuilder.builder(clazz.simpleName)
                    .selection(condition.selection, condition.selectionArgs)
                    .groupBy(condition.groupBy)
                    .orderBy(condition.orderBy)
                    .limit(condition.limit)
                    .having(condition.having).create()
                dao.select(query)
            }
        }
    }

    override fun removeOldCache(condition: Condition) {
        runBlocking {
            withContext(Dispatchers.IO) {
                val model = queryCache(condition)
                model?.let {
                    val ok = dao.delete(it) > 0
                    OrmLog.d("removeOldCache:$ok")
                }
            }
        }
    }

    override fun addNewCache(models: MutableList<M>) {
        runBlocking {
            withContext(Dispatchers.IO) {
                val results = dao.insert(models)
                for (result in results) {
                    if (result > 0) {
                        continue
                    } else {
                        OrmLog.d("addNewCache:false")
                        return@withContext
                    }
                }
                OrmLog.d("addNewCache:true")
            }
        }
    }

    override fun queryCacheSize(condition: Condition): Long {
        val query = RoomSQLiteQuery.acquire("SELECT * FROM " + clazz.simpleName + " WHERE "
                + condition.selection, condition.selectionArgs.size)
        val cursor = runBlocking {
            withContext(Dispatchers.IO) {
                db.query(query)
            }
        }
        if (cursor != null) {
            return try {
                if (cursor.moveToFirst()) {
                    cursor.getInt(0).toLong()
                } else 0
            } finally {
                cursor.close()
                query.release()
            }
        }
        return 0
    }
}
```
由于Room需要配合协程去操作数据库,所以你也要依赖协程的库。
```
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
def room_version = "2.2.0-rc01"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
```
由于Room源码使用到了apt,你也要配置apt的环境。
```
plugins {
    id 'kotlin-kapt'
}
```
这里也有可能是apply。
```
android {
    defaultConfig{
        javaCompileOptions {
            annotationProcessorOptions {
            arguments = [
                    "room.schemaLocation":"$projectDir/schemas".toString(),
                    "room.incremental":"true",
                    "room.expandProjection":"true"]
            }
        }
    }
}
```
由于Room的Dao的基于接口的,它底层使用了apt去生成了XxxDao_Impl类,我们干脆直接定义两个顶层的接口,IRoomDao和IListRoomDao。
```
package dora.cache.holder.dao

import androidx.room.Delete
import androidx.room.Insert
import androidx.room.RawQuery
import androidx.sqlite.db.SupportSQLiteQuery

interface IRoomDao<T> {

    @RawQuery
    suspend fun select(query: SupportSQLiteQuery) : T

    @Insert
    suspend fun insert(model: T) : Long

    @Delete
    suspend fun delete(model: T) : Int
}
```
```
package dora.cache.holder.dao

import androidx.room.Delete
import androidx.room.Insert
import androidx.room.RawQuery
import androidx.sqlite.db.SupportSQLiteQuery

interface IListRoomDao<T> {

    @RawQuery
    suspend fun select(query: SupportSQLiteQuery) : MutableList<T>

    @Insert
    suspend fun insert(models: MutableList<T>) : MutableList<Long>

    @Delete
    suspend fun delete(models: MutableList<T>) : Int
}
```
我们也来复习一下Room数据库的使用。
```
@Database(entities = [Book::class], version = 1)
abstract class BookDatabase : RoomDatabase() {

    abstract fun bookDao() : BookDao
}
```
先定义一个RoomDatabase的子类。
```
@Entity
@TypeConverters(BookConverter::class)
data class Book(
    @PrimaryKey(autoGenerate = true) var id: Long,
    val bookName: String,
    val bookPageCount: Int,
    val bookAuthorInfo: BookAuthorInfo? = null
)
```

```
@Repository(isListMode = false)
class BookRepository(context: Context) : RoomDatabaseCacheRepository<Book, BookDatabase>(context) {

    private lateinit var someParams: String

    fun initParams(someParams: String) {
        this.someParams = someParams
    }

    override fun query(): Condition {
        return WhereBuilder.create().addWhereEqualTo("xx", "xx").toCondition()
    }

    override fun onLoadFromNetworkObservable(): Observable<Book> {
        return get(BookService::class.java)
            .getBook()
            .compose<BaseResponse<Book>>(RxUtils.exceptionIoTransformer()).map {
                it.data
            }
    }

    override fun getRoomDatabase(): BookDatabase {
        return Room.databaseBuilder(context, BookDatabase::class.java, "db_book").build()
    }

    override fun getDaoName(): String {
        return "bookDao"
    }
}
```
```
@Dao
interface BookDao : IRoomDao<Book> {

    @RawQuery(observedEntities = [Book::class])
    override suspend fun select(query: SupportSQLiteQuery): Book

    @Insert
    override suspend fun insert(model: Book): Long

    @Delete
    override suspend fun delete(model: Book): Int
}
```
另外,我们把Condition也强化一下。
```

fun Condition.having(): String {
    return appendClause(" HAVING ", having)
}

fun Condition.orderBy(): String {
    return appendClause(" ORDER BY ", orderBy)
}

fun Condition.groupBy(): String {
    return appendClause(" GROUP BY ", groupBy)
}

fun Condition.limit(): String {
    return appendClause(" LIMIT ", limit)
}

private fun appendClause(name: String, clause: String?) : String {
    val s = StringBuilder()
    if (!isEmpty(clause)) {
        s.append(name)
        s.append(clause)
    }
    return s.toString()
}

/**
 * 将查询条件部分转化为sql。
 */
fun Condition.toSQL() : String {
    val sb = StringBuilder()
    if (selection != "") {
        if (!selection.contains("?")) {
            sb.append("WHERE").append(" ").append(selection)
        } else {
            val hasPrefixHolder = selection.startsWith("?")
            if (hasPrefixHolder) {
                throw IllegalArgumentException("selection can't start with ?")
            }
            val hasSuffixHolder = selection.endsWith("?")
            val specs = selection.split("?")
            specs.forEachIndexed {
                index, element ->
                if (index <= specs.size - 2) {
                    sb.append(element).append(selectionArgs[index])
                }
                if (hasSuffixHolder && index == specs.size - 1) {
                    sb.append(element).append(selectionArgs[index])
                }
            }
        }
        sb.append(groupBy() + having() + orderBy() + limit())
    }
    return sb.toString()
}
```
Condition作为查询条件的协议,通过QueryBuilder和WhereBuilder构建出来的,你一定要好好掌握。不管是内置的ORM框架、Room,又或者是GreenDao,Condition都是通用的。也欢迎各位自己做Orm数据库的整合,并提出宝贵的意见,这样你对框架的理解可以更上一层楼,框架也会因为你而不断完善。瑞思拜!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dora丶Android

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值