2. 搭建 Room 项目环境:添加依赖、配置 KSP/Gradle、初始化数据库、定义实体与 DAO 基础结构
2.1 为什么需要独立搭建环境?
Room 是 Android Jetpack 的持久化库,它在 SQLite 之上提供了编译时校验、自动生成 DAO 实现、与 LiveData/Flow 天然集成等能力。搭建时如果依赖版本不一致、注解处理器(KSP 或 kapt)配置错误,会导致编译失败或运行时 DAO 方法找不到。本章定出标准化搭建步骤:添加依赖 → 配置 KSP → 编写实体 → 创建 DAO → 构建 Database 类 → 初始化实例。
2.2 Gradle 依赖与 KSP 配置
Room 从 2.4.0 起推荐使用 KSP(Kotlin Symbol Processing)替代 kapt,编译速度提升 2~3 倍。以下在 build.gradle.kts(Module 级)中配置:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp") version "1.9.22-1.0.17" // 与 Kotlin 版本匹配
}
android {
namespace = "com.example.roomcache"
compileSdk = 34
defaultConfig {
minSdk = 26
targetSdk = 34
}
}
dependencies {
val roomVersion = "2.6.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
// optional: Room test helpers
testImplementation("androidx.room:room-testing:$roomVersion")
}
使用 Kotlin DSL 时注意:ksp 配置块需要与 com.google.devtools.ksp 插件配合。如果项目已有 Java 代码,可以保留 kapt,但建议统一使用 KSP。
2.3 定义实体 Entity
实体对应数据库表,用 @Entity 注解标记。增量更新缓存场景中,常用字段包含 id(主键)、data(JSON 数据)、timestamp(版本时间戳):
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "cache_entries")
data class CacheEntry(
@PrimaryKey
val key: String, // 缓存唯一标识(如 API 路径)
val data: String, // 缓存数据(一般序列化为 JSON)
val timestamp: Long, // 最后更新时间戳(毫秒)
val version: Int = 1 // 增量版本号,初始为 1
)
实体设计提示:
version 字段用于增量更新时比较本地版本与服务器端版本,避免全量替换。timestamp 可用于过期策略。2.4 创建 DAO(Data Access Object)
DAO 负责数据库读写,使用 @Dao 注解,内定义查询、插入、更新、删除方法:
import androidx.room.*
@Dao
interface CacheDao {
@Query("SELECT * FROM cache_entries WHERE `key` = :key")
suspend fun getCache(key: String): CacheEntry?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCache(entry: CacheEntry)
@Query("UPDATE cache_entries SET data = :data, timestamp = :timestamp, version = :version WHERE `key` = :key")
suspend fun updateCache(key: String, data: String, timestamp: Long, version: Int)
@Query("DELETE FROM cache_entries WHERE `key` IN (:keys)")
suspend fun deleteCaches(keys: List)
}
| 注解 | 作用 | 示例 |
|---|---|---|
@Query | 编写自定义 SQL,自动返回 DAO 方法结果 | @Query("SELECT * FROM cache_entries") |
@Insert | 插入一条或多条记录,支持冲突策略 | @Insert(onConflict = OnConflictStrategy.REPLACE) |
@Update | 更新已有记录(根据主键匹配) | @Update |
@Delete | 删除记录 | @Delete |
关键区别:
@Insert(onConflict = REPLACE) 在插入冲突时会完全替换整行,而 @Update 只更新非空字段。增量更新策略中建议使用 @Insert(REPLACE) 简化逻辑。2.5 构建 Database 类
继承 RoomDatabase,声明实体列表、DAO 抽象方法,并定义单例数据库构建器:
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(
entities = [CacheEntry::class],
version = 1,
exportSchema = true // 导出 schema 用于增量迁移
)
abstract class AppDatabase : RoomDatabase() {
abstract fun cacheDao(): CacheDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"room_cache.db"
)
.fallbackToDestructiveMigration() // 开发期临时使用(生产应写 Migration)
.build()
INSTANCE = instance
instance
}
}
}
}
注意:
fallbackToDestructiveMigration() 会在数据库版本升级且未提供 Migration 时清除所有数据。生产环境必须编写 Migration 对象实现增量迁移,否则用户数据将丢失。2.6 初始化数据库(Application 中)
通常在 Application 的 onCreate 中预初始化,避免首次打开页面时的延迟:
import android.app.Application
class MyApp : Application() {
lateinit var database: AppDatabase
private set
override fun onCreate() {
super.onCreate()
database = AppDatabase.getDatabase(this)
}
}
别忘了在 AndroidManifest.xml 中注册:
<application
android:name=".MyApp"
... >
...
</application>
2.7 搭建流程总览
2.8 常见错误与校验
- KSP 版本与 Kotlin 不匹配:报错
Module was compiled with an incompatible version of Kotlin。检查classpath "org.jetbrains.kotlin:kotlin-gradle-plugin"版本与 KSP 版本对应关系(参考 KSP Releases)。 - DAO 方法未生成:忘记添加
ksp或误用kapt但未配置kapt依赖。确认 build.gradle 中ksp("androidx.room:room-compiler:...")存在且插件生效。 - 实体字段未列在表中:Room 默认只保留
@PrimaryKey和@ColumnInfo注解字段。如果漏加@ColumnInfo,字段名会与 Kotlin 属性名一致,但可以通过@ColumnInfo(name = "column_name")显式定义。
性能优化基线:数据库初始化建议放在后台线程(如
Dispatchers.IO)执行,避免阻塞主线程。可以在 Application 中启动协程预加载。// 预初始化示例
GlobalScope.launch(Dispatchers.IO) {
AppDatabase.getDatabase(this@MyApp).cacheDao().insertCache(
CacheEntry(key = "_init", data = "{}", timestamp = System.currentTimeMillis())
)
}