Jetpack系列(五) — Room
Room简单介绍
初步印象
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
基本概念
@Database
标记数据库,继承RoomDatabase
,执行数据库创建相关的操作
@Entity
标记表,将类标记为实体。这个类在数据库中将有一个映射SQLite表
@Dao
标记数据操作的类
Room基本使用
实现本地存储
-
创建表,用
@Entity
标记- 可以使用
@Embedded
注释表示要分解为表格中的子字段的对象
// Word.kt @Entity(tableName = "word_table") data class Word( @PrimaryKey @ColumnInfo(name = "word") val word: String ) 复制代码
- 可以使用
-
创建数据库操作类,用
@Dao
标记@Insert
、@Delete
、@Update
等操作均可使用@Query
代替Room
查询操作可以自动返回Flow
@Dao interface WordDao { @Query("SELECT * FROM word_table ORDER BY word ASC") fun getAlphabetizedWords(): Flow<List<Word>> // 主键冲突 @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insert(word: Word) @Query("DELETE FROM word_table") suspend fun deleteAll() } 复制代码
-
创建
Room Database
,继承RoomDatabase
, 使用@Database
标记@Database( entities = [Word::class], // 表 version = 1, exportSchema = false ) abstract class AppDataBase : RoomDatabase() { abstract fun wordDao(): WordDao companion object { @Volatile private var INSTANCE: AppDataBase? = null fun getDataBase( context: Context, scope: CoroutineScope ): AppDataBase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDataBase::class.java, "my_database" ).build() INSTANCE = instance instance } } } } 复制代码
-
在
Repository
中引入Dao
class WordRepository( private val wordDao: WordDao ) { val allWords: Flow<List<Word>> = wordDao.getAlphabetizedWords() @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun insert(word: Word) { wordDao.insert(word) } } 复制代码
-
ViewModel
当中引入Repository
class MainViewModel( private val repository: WordRepository ) : ViewModel() { val allWords: LiveData<List<Word>> = repository.allWords.asLiveData() fun insert(word: Word) = viewModelScope.launch { repository.insert(word) } } // 用于实例化MainViewModel class MainViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return MainViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } } 复制代码
-
在
View
层修改和观察数据// HomeFragment.kt 观察数据 class HomeFragment : Fragment() { private lateinit var binding: FragmentHomeBinding private lateinit var adapter: WordListAdapter private val mainViewModel: MainViewModel by activityViewModels { MainViewModelFactory((requireActivity().application as BaseApplication).repository) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = FragmentHomeBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initView() initListener() subscribeUI() } private fun initView() { adapter = WordListAdapter() binding.recyclerview.adapter = adapter } private fun initListener() { ... } private fun subscribeUI() { observe(mainViewModel.allWords, ::allWordsUpdate) } private fun allWordsUpdate(words: List<Word>) { words.let { adapter.submitList(words) } } } // FlowStepFragment.kt 新增数据 class FlowStepFragment : Fragment() { private lateinit var binding: ViewBinding private val viewModel: MainViewModel by activityViewModels { MainViewModelFactory((requireActivity().application as BaseApplication).repository) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { ... binding = when (flowStepNumber) { 2 -> FragmentFlowStepTwoBinding.inflate(inflater, container, false) else -> FragmentFlowStepOneBinding.inflate(inflater, container, false) } return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val mainActivity = activity as MainActivity mainActivity.dismissBottom() initView() initListener(view) subscribeUI() } private fun initView() { } private fun initListener(view: View) { view.findViewById<Button>(R.id.button)?.setOnClickListener( Navigation.createNavigateOnClickListener(R.id.next_action) ) view.findViewById<Button>(R.id.button_add).setOnClickListener { val word = view.findViewById<EditText>(R.id.et_word).text.toString() if (word.isEmpty()) { return@setOnClickListener } // 插入数据库 viewModel.insert(Word(word)) // 返回 Navigation.findNavController(it).navigateUp() } } private fun subscribeUI() { } } 复制代码
-
Application
中间实例化database
-
WordRepository
构造函数需要传入WordDao
,WordDao
是由AppDataBase
创建,案例当中直接在Application
创建 -
HomeFragment
和FlowStepFragment
当中就可以从Application
拿到WordRepository
的实例
class BaseApplication : Application() { private val database by lazy { AppDataBase.getDataBase(thise) } val repository by lazy { WordRepository(database.wordDao()) } } 复制代码
-
数据库迁移
-
RoomDatabase
当中有addMigrations
- 下述代码就是当手机原本是版本1新代码是版本2的时候,更新之后会创建一个表
// 代码来自 《第一行代码》 private val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("create table Book (id integer primary key autoincrement not null, name text not null, pages integer not null)") } } @Synchronized fun getDatabase(context: Context): AppDatabase { instance?.let { return it } return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database") .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .build().apply { instance = this } } 复制代码
相关知识点
知识点一: 查看数据文件
Android Studio
插件Database Navigator
tab
标签DB Browser
知识点二: 类型转换器
-
使用
@TypeConverter
注解, 可以实现在自定义类与 Room 可以保留的已知类型之间来回转换// 官网例子 class Converters { @TypeConverter fun fromTimestamp(value: Long?): Date? { return value?.let { Date(it) } } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time?.toLong() } } 复制代码
-
在
dataBase
类上加注解@Database
@Database(entities = arrayOf(User::class), version = 1) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } 复制代码
知识点三: 预填充数据库
-
使用
createFromAsset()
或createFromFile()
预填充数据库 -
结合
fallbackToDestructiveMigration()
增加基础数据数据@Database( entities = [Word::class], version = 1, exportSchema = false ) abstract class AppDataBase : RoomDatabase() { abstract fun wordDao(): WordDao companion object { @Volatile private var INSTANCE: AppDataBase? = null fun getDataBase( context: Context, scope: CoroutineScope ): AppDataBase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDataBase::class.java, "my_database" ) .fallbackToDestructiveMigration() .addCallback(WordDatabaseCallback(scope)) .build() INSTANCE = instance instance } } class WordDatabaseCallback( private val scope: CoroutineScope ) : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) INSTANCE?.let {database -> scope.launch(Dispatchers.IO) { populateDatabase(database.wordDao()) } } } } private suspend fun populateDatabase(wordDao: WordDao) { wordDao.deleteAll() var word = Word("Hello") wordDao.insert(word) word = Word("World!") wordDao.insert(word) } } } 复制代码
相关链接
参考资料
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END