使用Hilt注入Worker预填充Room数据库时的读取时机问题
解决Room预填充数据晚于首次读取的问题
你的问题核心是:Room数据库创建时通过WorkManager异步执行预填充,但数据库可用后立即被读取,此时Worker的插入操作尚未完成,导致首次读取无数据,重试才正常。下面是三个可行的解决方案:
方案一:同步执行预填充(替代WorkManager)
直接在Room的onCreate回调中同步完成数据插入,确保数据库可用前数据已填充完毕。修改Hilt模块中的provideDatabaseCallback:
@Provides @Singleton fun provideDatabaseCallback( @ApplicationContext context: Context, beerDao: BeerDao ) = object : RoomDatabase.Callback() { @OptIn(ExperimentalSerializationApi::class) override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) // 用runBlocking在IO线程同步执行预填充,阻塞到插入完成 runBlocking(Dispatchers.IO) { runCatching { context.assets.open(DatabaseWorker.prePopulateDatabaseFile).use { inputStream -> val beers = Json.decodeFromStream<List<BeerDatabaseModel>>(inputStream) beerDao.insertBeers(beers) } }.onFailure { Log.e("DatabaseCallback", "预填充失败", it) } } } }
优点:逻辑简单,彻底避免异步时序问题;缺点:如果预填充数据量极大,会阻塞数据库初始化线程(若主线程触发数据库初始化,可能导致启动卡顿),建议确保首次数据库访问在后台线程。
方案二:读取数据前等待WorkManager任务完成
若必须保留WorkManager的异步执行逻辑,可在数据读取前等待预填充任务完成。以Repository层为例:
class BeerRepository @Inject constructor( private val beerDao: BeerDao, private val workManager: WorkManager ) { suspend fun getBeers(beerName: String?, offset: Int, limitPerPage: Int): List<BeerDatabaseModel> { // 等待预填充任务结束后再读取数据 waitForPrepopulateCompletion() return beerDao.getBeers(beerName, offset, limitPerPage) } private suspend fun waitForPrepopulateCompletion() { val initialWorkInfo = workManager.getWorkInfosForUniqueWorkLiveData(beerDatabaseWorkerName) .asFlow() .firstOrNull { it.isNotEmpty() } ?.first() // 若任务未完成,等待到结束 if (initialWorkInfo?.state?.isFinished != true) { workManager.getWorkInfosForUniqueWorkLiveData(beerDatabaseWorkerName) .asFlow() .filter { it.isNotEmpty() && it.first().state.isFinished } .first() } } }
优点:保留WorkManager的异步调度优势(如任务重试、后台执行);缺点:首次读取会等待任务完成,增加响应延迟。
方案三:使用Room官方createFromAsset预填充
如果预填充数据是静态文件,Room官方提供了直接从assets目录加载数据的API,无需手动写Worker或回调,这是最简洁的方案:
修改Hilt模块中的provideRoomDb:
@Provides @Singleton fun provideRoomDb( @ApplicationContext context: Context ): BeerAppDatabase = Room .databaseBuilder(context, BeerAppDatabase::class.java, beerAppDatabaseName) .fallbackToDestructiveMigration() .createFromAsset("pre-populate-database.json") // 直接指定assets中的预填充文件 .build()
注意:assets中的JSON文件格式需符合要求:
- 根节点为数组,每个元素对应
BeerDatabaseModel实体 - 字段名需与实体类字段一致(或通过
@ColumnInfo映射)
示例JSON结构:
[ {"id": 1, "name": "IPA", "abv": 6.5}, {"id": 2, "name": "Stout", "abv": 8.0} ]
优点:官方原生支持,性能最优,无异步时序问题;缺点:仅适用于静态预填充数据,无法动态生成数据。
内容的提问来源于stack exchange,提问作者Barros
相关产品推荐
相关产品推荐

