当Room数据库遇上ViewModel:Android数据持久化的"相声专场"

270 阅读4分钟

当Room数据库遇上ViewModel:Android数据持久化的"相声专场"

各位码农朋友们,今天我们要聊的是Android开发界的"黄金搭档"——Room数据库和ViewModel。这对CP就像郭德纲和于谦,一个负责说学逗唱(数据存储),一个负责捧哏控场(数据管理),组合起来能让你的代码既严谨又有趣。

一、Room数据库:SQLite的"整容医生"

Room数据库是Google给SQLite做的"美颜手术",通过注解系统让数据库操作变得像谈恋爱一样简单^[2][8]^。它用三个核心组件构建了整个数据库体系:

  1. 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
)
  1. 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>>
}
  1. 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的"甜蜜互动"

  1. 创建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)
    }
}
  1. 在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时,可以分三步走:

  1. 提取数据逻辑:将所有数据库操作移到Repository层
  2. 创建ViewModel:封装数据和业务逻辑
  3. 更新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时,每次旋转屏幕就像在玩"数据消失"的魔术吗?现在有了这对黄金搭档,终于可以让我们的数据"安分守己"了!