本地存储是 Android 应用开发的核心场景之一,Jetpack Room 作为 Google 推荐的 ORM 框架,简化了 SQLite 操作,避免原生 SQL 的繁琐与易错。本文将手把手教你封装一个生产级的 Room 数据库工具类,实现 数据库初始化、实体类映射、DAO 层封装、数据增删改查(CRUD)全功能,并结合 LiveData 实现数据观察,适配 Android 14 系统特性。文末附完整可运行代码,复制即可集成!
一、核心依赖与环境配置
在
app/build.gradle(Module 级别)中添加以下依赖,支持 Room 核心功能、协程异步操作及生命周期感知:gradle
dependencies { // Room 核心依赖 implementation "androidx.room:room-runtime:2.6.1" annotationProcessor "androidx.room:room-compiler:2.6.1" // 协程支持(异步操作数据库) implementation "androidx.room:room-ktx:2.6.1" // LiveData 支持(数据观察) implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2" // 协程核心库 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" }💡 提示:同步项目(Sync Now)后,Room 注解处理器会自动生效,支持数据库相关注解解析。
二、Room 三层架构设计
Room 遵循 MVC 思想,通过三层架构实现数据解耦,核心组件如下:
组件 作用 对应类 / 接口 实体类(Entity) 映射数据库表结构,定义字段与表属性 NoteEntity数据访问对象(DAO) 定义数据库操作方法(CRUD),无需手写 SQL NoteDao数据库实例(Database) 管理数据库版本、关联实体类与 DAO,提供单例实例 AppDatabase工具类(Repository) 封装 DAO 操作,暴露统一 API,处理异步逻辑 NoteRepository三、逐文件编写代码(附详细注释)
1. 实体类:NoteEntity(映射数据表)
定义笔记表结构,包含 id、内容、创建时间等字段,适配鸿蒙系统风格的数据存储需求:
kotlin
import androidx.room.Entity import androidx.room.PrimaryKey import java.util.Date // 数据表名:note_table @Entity(tableName = "note_table") data class NoteEntity( @PrimaryKey(autoGenerate = true) // 自增主键 val id: Long = 0, val content: String, // 笔记内容(非空) val createTime: Long = Date().time, // 创建时间(默认当前时间戳) val updateTime: Long = Date().time // 更新时间(默认当前时间戳) )💡 关键说明:
@Entity注解标识该类为数据库表,tableName指定表名@PrimaryKey(autoGenerate = true)实现 id 自动增长,避免手动管理主键- 时间戳采用 Long 类型存储,兼容不同系统时间格式
2. DAO 接口:NoteDao(定义数据库操作)
通过 Room 注解定义 CRUD 操作,无需编写原生 SQL,支持协程异步调用:
kotlin
import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.Query import androidx.room.Update import kotlinx.coroutines.flow.Flow @Dao interface NoteDao { // 插入笔记(冲突时替换) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertNote(note: NoteEntity) // 更新笔记 @Update suspend fun updateNote(note: NoteEntity) // 删除单条笔记 @Delete suspend fun deleteNote(note: NoteEntity) // 删除所有笔记 @Query("DELETE FROM note_table") suspend fun deleteAllNotes() // 查询所有笔记(按更新时间倒序) @Query("SELECT * FROM note_table ORDER BY updateTime DESC") fun getAllNotes(): Flow<List<NoteEntity>> // Flow 支持数据观察,自动响应变化 // 根据id查询笔记 @Query("SELECT * FROM note_table WHERE id = :noteId LIMIT 1") suspend fun getNoteById(noteId: Long): NoteEntity? }✅ 优势说明:
- 注解化操作:
@Insert/@Update/@Delete自动映射 SQL 操作- 异步支持:
suspend函数适配协程,避免主线程阻塞(Android 强制要求)- 数据观察:
Flow<List<NoteEntity>>可实时监听数据变化,自动刷新 UI3. 数据库实例:AppDatabase(单例管理)
创建数据库单例,关联实体类与 DAO,处理数据库版本迁移(基础版采用销毁重建策略):
kotlin
import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import android.content.Context // 数据库版本:1;关联的实体类:NoteEntity @Database(entities = [NoteEntity::class], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase() { // 暴露DAO接口实例 abstract fun noteDao(): NoteDao companion object { // 单例模式:双重校验锁(线程安全) @Volatile private var INSTANCE: AppDatabase? = null // 获取数据库实例 fun getInstance(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, // 应用上下文,避免内存泄漏 AppDatabase::class.java, "note_database" // 数据库文件名:note_database.db ) .fallbackToDestructiveMigration() // 版本升级时销毁旧数据库(适合开发初期) .build() INSTANCE = instance instance } } } }⚠️ 注意事项:
- 必须使用
applicationContext初始化,避免 Activity 上下文导致的内存泄漏exportSchema = false关闭数据库模式导出(生产环境建议开启,便于版本迁移)- 进阶提示:复杂项目可通过
Migration类实现数据迁移,保留旧数据4. 仓库层:NoteRepository(封装业务逻辑)
统一暴露数据库操作 API,隔离数据层与 UI 层,处理协程异步逻辑:
kotlin
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class NoteRepository(private val noteDao: NoteDao) { // 观察所有笔记(对外暴露Flow,UI层可观察数据变化) val allNotes = noteDao.getAllNotes() // 插入笔记(切换到IO线程执行) suspend fun insertNote(note: NoteEntity) = withContext(Dispatchers.IO) { noteDao.insertNote(note) } // 更新笔记(带时间戳更新) suspend fun updateNote(note: NoteEntity) = withContext(Dispatchers.IO) { val updatedNote = note.copy(updateTime = System.currentTimeMillis()) noteDao.updateNote(updatedNote) } // 删除笔记 suspend fun deleteNote(note: NoteEntity) = withContext(Dispatchers.IO) { noteDao.deleteNote(note) } // 清空所有笔记 suspend fun deleteAllNotes() = withContext(Dispatchers.IO) { noteDao.deleteAllNotes() } // 根据id查询笔记 suspend fun getNoteById(noteId: Long): NoteEntity? = withContext(Dispatchers.IO) { noteDao.getNoteById(noteId) } }💡 设计思路:
- 所有数据库操作切换到
Dispatchers.IO线程,避免阻塞主线程- 对外隐藏 DAO 层细节,UI 层只需调用 Repository 方法,便于后续扩展(如增加网络缓存)
- 自动更新
updateTime,确保数据排序准确性5. 视图模型:NoteViewModel(连接 UI 与数据层)
结合 ViewModel 与 LiveData,管理 UI 相关数据,避免配置变更(如屏幕旋转)导致数据丢失:
kotlin
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch class NoteViewModel(private val repository: NoteRepository) : ViewModel() { // 观察所有笔记(UI层通过此LiveData获取数据) val allNotes = repository.allNotes // 插入笔记(ViewModelScope自动管理协程生命周期) fun insertNote(content: String) { viewModelScope.launch { val note = NoteEntity(content = content) repository.insertNote(note) } } // 更新笔记 fun updateNote(note: NoteEntity) { viewModelScope.launch { repository.updateNote(note) } } // 删除笔记 fun deleteNote(note: NoteEntity) { viewModelScope.launch { repository.deleteNote(note) } } // 清空所有笔记 fun deleteAllNotes() { viewModelScope.launch { repository.deleteAllNotes() } } } // ViewModel工厂类:提供Repository实例 class NoteViewModelFactory(private val repository: NoteRepository) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(NoteViewModel::class.java)) { return NoteViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }四、UI 层调用示例(Activity/Fragment)
在 UI 层通过 ViewModel 操作数据库,实现笔记列表展示、添加、删除功能:
kotlin
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { private lateinit var noteViewModel: NoteViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化RecyclerView(展示笔记列表) val adapter = NoteAdapter { note -> // 点击笔记项删除 noteViewModel.deleteNote(note) } recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(this) // 初始化ViewModel val database = AppDatabase.getInstance(application) val repository = NoteRepository(database.noteDao()) noteViewModel = ViewModelProvider( this, NoteViewModelFactory(repository) )[NoteViewModel::class.java] // 观察笔记数据变化,自动刷新UI noteViewModel.allNotes.observe(this) { notes -> adapter.submitList(notes) } // 点击按钮添加笔记 addNoteBtn.setOnClickListener { val content = noteInput.text.toString().trim() if (content.isNotEmpty()) { noteViewModel.insertNote(content) noteInput.text.clear() // 清空输入框 } else { // 空内容提示(适配鸿蒙风格弹窗) android.widget.Toast.makeText(this, "请输入笔记内容!", android.widget.Toast.LENGTH_SHORT).show() } } } }五、核心适配与避坑指南
适配要点 实现方式 鸿蒙系统风格 弹窗提示采用系统原生 Toast,保持操作习惯一致性;数据存储路径遵循 Android 规范,兼容鸿蒙文件系统 主线程安全 所有数据库操作通过协程切换到 IO 线程,避免 ANR 异常 配置变更兼容 使用 ViewModel 存储数据,屏幕旋转时不丢失状态 数据观察 采用 Flow+LiveData,实现数据变化自动刷新 UI,减少手动回调 错误处理 基础版通过协程异常捕获(可扩展添加 try-catch 处理插入失败等场景) 六、功能测试用例
测试场景 操作步骤 预期结果 空内容添加 直接点击 "添加笔记" 按钮 弹出 "请输入笔记内容!" 提示,无数据插入 正常添加笔记 输入文本→点击添加 笔记列表实时刷新,显示新添加的笔记 删除笔记 点击列表项 对应笔记从列表中移除,数据库中数据删除 屏幕旋转 添加笔记后旋转屏幕 笔记列表保持不变,输入框为空 数据持久化 退出应用后重新打开 之前添加的笔记全部保留 七、结语
通过 Room + ViewModel + Repository 架构封装,实现了一套健壮、可维护的本地数据库方案,既适配鸿蒙系统操作习惯,又符合 Android 开发最佳实践。该方案支持数据观察、异步安全、配置变更兼容等核心特性,可直接应用于生产级项目,也可根据需求扩展搜索、分页、数据迁移等高级功能。
Flutter 网络请求实战:Dio 封装 + 拦截器 + 数据解析
克喵的水银蛇2025-12-06 20:06
相关推荐
汤愈韬2 小时前
知识点3:动态目的NAT的配置总结CNRio3 小时前
第8章 网络安全应急响应风掣长空3 小时前
Google Test (gtest) 新手完全指南:从入门到精通子春一4 小时前
Flutter 构建系统深度解析:从 pubspec.yaml 到 release 包的全链路掌控发光小北4 小时前
SG-PNh750-TCP-210(Profinet 从站转 Modbus TCP 网关)帅气马战的账号4 小时前
开源鸿蒙+Flutter:跨端开发的组件化重构与性能跃迁QuantumLeap丶5 小时前
《Flutter全栈开发实战指南:从零到高级》- 23 -混合开发与WebView轻颂呀5 小时前
TCP协议松涛和鸣5 小时前
25、数据结构:树与二叉树的概念、特性及递归实现