当Room数据库遇上ViewModel:Android数据持久化的"相声专场"
各位码农朋友们,今天我们要聊的是Android开发界的"黄金搭档"——Room数据库和ViewModel。这对CP就像郭德纲和于谦,一个负责说学逗唱(数据存储),一个负责捧哏控场(数据管理),组合起来能让你的代码既严谨又有趣。
一、Room数据库:SQLite的"整容医生"
Room数据库是Google给SQLite做的"美颜手术",通过注解系统让数据库操作变得像谈恋爱一样简单^[2][8]^。它用三个核心组件构建了整个数据库体系:
- Entity(实体类):相当于数据库表的Java/Kotlin镜像
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
@ColumnInfo(name = "user_name") val name: String,
@ColumnInfo(name = "user_age") val age: Int
)
- DAO(数据访问对象):定义CRUD操作的接口
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User)
@Query("SELECT * FROM users WHERE age > :minAge")
fun getAdults(minAge: Int): LiveData<List<User>>
}
- Database(数据库类):管理所有DAO的入口
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
💡 幽默时刻:Room就像给SQLite穿了件高定西装,让原本需要手写SQL的"原始人"操作,变成了优雅的注解式编程。
二、ViewModel:Activity的"备胎情人"
在遇到ViewModel之前,我们的Activity就像个"恋爱脑",屏幕旋转就重置所有数据。ViewModel的出现,让数据管理有了"正宫"的稳定感^[1][5]^。
新项目标配:Room+ViewModel的"甜蜜互动"
- 创建ViewModel:
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val db = Room.databaseBuilder(
application,
AppDatabase::class.java, "user_db"
).build()
val adults: LiveData<List<User>> = db.userDao().getAdults(18)
fun addUser(user: User) = viewModelScope.launch {
db.userDao().insert(user)
}
}
- 在Activity中使用:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
viewModel.adults.observe(this) { users ->
// 更新UI
}
binding.addButton.setOnClickListener {
viewModel.addUser(User(name = "张三", age = 25))
}
}
}
🎯 优势对比:
- 传统方式:屏幕旋转导致数据丢失
- ViewModel方式:数据在配置变更后依然坚挺
- 就像把钱包从裤兜转移到保险箱,安全感爆棚
三、老项目改造:没有ViewModel的"单身生活"
对于没有使用ViewModel的老项目,我们依然可以让Room发挥威力,只是需要多些"手动操作":
1. 直接在Activity/Fragment中操作Room
class LegacyActivity : AppCompatActivity() {
private lateinit var db: AppDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "legacy_db"
).build()
// 直接查询
lifecycleScope.launch {
val users = db.userDao().getAdults(18)
// 更新UI
}
// 直接插入
binding.addButton.setOnClickListener {
lifecycleScope.launch {
db.userDao().insert(User(name = "李四", age = 30))
refreshData()
}
}
}
private suspend fun refreshData() {
val users = db.userDao().getAdults(18)
withContext(Dispatchers.Main) {
// 更新UI
}
}
}
2. 使用单例模式管理数据库(谨慎使用)
object DatabaseManager {
private var instance: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return instance ?: synchronized(this) {
val db = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, "singleton_db"
).build()
instance = db
db
}
}
}
// 使用
val db = DatabaseManager.getDatabase(context)
⚠️ 警告:单例模式虽然方便,但可能引发内存泄漏,就像把家门钥匙交给所有邻居保管。
四、实战案例:待办事项应用
让我们通过一个完整案例,看看Room在不同架构下的表现:
1. 定义实体和DAO
@Entity(tableName = "tasks")
data class Task(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val completed: Boolean = false
)
@Dao
interface TaskDao {
@Insert
suspend fun insert(task: Task)
@Query("SELECT * FROM tasks WHERE completed = 0")
fun getPendingTasks(): LiveData<List<Task>>
@Update
suspend fun update(task: Task)
}
2. 现代架构实现(ViewModel版)
class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val db = Room.databaseBuilder(
application,
AppDatabase::class.java, "task_db"
).build()
val pendingTasks: LiveData<List<Task>> = db.taskDao().getPendingTasks()
fun completeTask(task: Task) = viewModelScope.launch {
db.taskDao().update(task.copy(completed = true))
}
}
3. 传统架构实现(无ViewModel版)
class LegacyTaskActivity : AppCompatActivity() {
private lateinit var db: AppDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "legacy_task_db"
).build()
lifecycleScope.launch {
db.taskDao().getPendingTasks().observe(this@LegacyTaskActivity) { tasks ->
// 更新UI
}
}
binding.completeButton.setOnClickListener {
val task = // 获取当前任务
lifecycleScope.launch {
db.taskDao().update(task.copy(completed = true))
refreshTasks()
}
}
}
private suspend fun refreshTasks() {
db.taskDao().getPendingTasks().observe(this) { tasks ->
withContext(Dispatchers.Main) {
// 更新UI
}
}
}
}
五、迁移策略:给老项目"换心脏"
当需要从无ViewModel架构迁移到MVVM时,可以分三步走:
- 提取数据逻辑:将所有数据库操作移到Repository层
- 创建ViewModel:封装数据和业务逻辑
- 更新UI层:让Activity/Fragment只负责显示
// 迁移前
class OldActivity : AppCompatActivity() {
fun loadData() {
// 直接操作数据库
}
}
// 迁移后
class NewActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.data.observe(this) {
// 更新UI
}
}
}
六、总结:选择适合你的"恋爱方式"
| 特性 | Room+ViewModel | 纯Room实现 |
|---|---|---|
| 数据持久性 | ✅ 优秀(ViewModel缓存) | ⚠️ 需手动处理 |
| 代码可维护性 | ✅ 高(分离关注点) | ⚠️ 中(Activity臃肿) |
| 配置变更处理 | ✅ 自动处理 | ❌ 需手动保存状态 |
| 适合场景 | 新项目/重构项目 | 快速原型/小型项目 |
就像选择恋爱对象,ViewModel提供了更稳定的关系,而直接使用Room则像自由恋爱——更灵活但需要更多维护。根据你的项目阶段和团队情况,选择最适合的方案吧!
🎤 最后吐槽:还记得没有ViewModel时,每次旋转屏幕就像在玩"数据消失"的魔术吗?现在有了这对黄金搭档,终于可以让我们的数据"安分守己"了!