Kotlin Android协程问题:挂起函数未在后台运行引发异常
嘿,我来帮你拆解这段代码里的问题,你遇到的IllegalStateException大概率是这两个核心原因导致的:
1. 重复创建Room数据库实例
Room有个硬性规则:同一个数据库类只能存在一个实例。你看你的store函数,每次调用它都会执行Room.databaseBuilder(...).build(),这相当于每次存数据都新建一个数据库连接实例,Room肯定会炸锅给你抛异常。
修复方法:把数据库做成全局单例
你应该在整个App生命周期里只初始化一次数据库实例,比如在你的Application类里用懒加载创建:
class MyApp : Application() { companion object { lateinit var db: AppDatabase } override fun onCreate() { super.onCreate() db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build() } }
之后在store函数里直接复用这个全局单例,再也不要每次新建了。
2. 在主线程执行数据库操作
你的retrieveAndStore函数用launch(UI)(也就是主线程)启动协程,然后直接调用suspend的store函数——但suspend函数不会自动切换线程,它会继承调用方的线程上下文,也就是说你的数据库插入操作是在主线程执行的。而Room默认禁止在主线程做数据库操作(会阻塞UI,拖慢App体验),这也会触发IllegalStateException。
修复方法:切换到IO线程执行数据库操作
在store函数里用withContext(Dispatchers.IO)把数据库操作切换到IO线程,同时别忘了修正你代码里的小bug(你传的参数是data,但插入的是storyData,这会导致变量未定义或者数据错误):
private suspend fun store(data: JsonData) { withContext(Dispatchers.IO) { MyApp.db.appDao().insert(data) } }
最后整理后的完整代码参考:
private fun retrieveAndStore() { launch(Dispatchers.Main) { // 建议用标准的Dispatchers.Main代替UI val service = retrofit.create(AWSService::class.java) val response = service.retrieveData().await() // 如果retrieveData是挂起函数,其实不用await,直接调用就行 store(data = response) } } private suspend fun store(data: JsonData) { withContext(Dispatchers.IO) { MyApp.db.appDao().insert(data) } } // 对应的Application类 class MyApp : Application() { companion object { lateinit var db: AppDatabase } override fun onCreate() { super.onCreate() db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build() } }
另外提一句,如果你的Retrofit接口用的是Coroutine Call Adapter,retrieveData应该定义成挂起函数(比如 suspend fun retrieveData(): JsonData),这样调用的时候就不用.await()了,直接val response = service.retrieveData()就行,更简洁。
内容的提问来源于stack exchange,提问作者SteedsOfWar

