移除suspend修饰符触发No virtual method错误的原因解析
我在编写从Room获取数据的代码时,IDE提示suspend是无用修饰符。移除该修饰符后,程序抛出如下错误:
E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1 Process: com.example.lightweight, PID: 6483 java.lang.NoSuchMethodError: No virtual method getWorkoutList(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; in class Lcom/example/lightweight/data/repository/WorkoutListRepository; or its super classes (declaration of 'com.example.lightweight.data.repository.WorkoutListRepository' appears in /data/data/com.example.lightweight/code_cache/.overlay/base.apk/classes13.dex) at com.example.lightweight.presentation.viewmodel.WorkoutListViewModel$getList$1.invokeSuspend(WorkoutListViewModel.kt:23) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42) at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664) Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@6975010, Dispatchers.IO] I/Process: Sending signal. PID: 6483 SIG: 9
但保留suspend修饰符时程序运行正常,请问这是什么原因?
相关代码
DAO
@Dao interface WorkoutDao { @Query("SELECT * From WorkoutListTest") fun getWorkoutList() : List<WorkoutListTest> }
ViewModel
class WorkoutListViewModel( private val repository: WorkoutListRepository ) : ViewModel() { private var _list = MutableLiveData<List<String>>() val list: LiveData<List<String>> get() = _list fun getList() { viewModelScope.launch(Dispatchers.IO) { repository.getWorkoutList() } } }
Repository
class WorkoutListRepository(private val dao: WorkoutDao) { fun getWorkoutList() : List<WorkoutListTest> { val list: List<WorkoutListTest> = dao.getWorkoutList() return list } }
Fragment中使用ViewModel的代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) vm.getList() vm.list.observe(viewLifecycleOwner) { list -> } }
核心问题:编译缓存不匹配 + Room查询的协程规范
编译缓存导致方法签名冲突
之前你给getWorkoutList加过suspend修饰符,编译后这个方法会自动带上Continuation类型的参数(协程底层用来处理回调的参数)。后来你移除了suspend,但IDE的编译缓存没清干净,旧的ViewModel字节码还在调用带Continuation的方法,而新编译的Repository里已经是无参数的普通方法了,直接导致NoSuchMethodError。IDE提示
suspend无用的误区
现在你的DAO用的是同步查询(fun返回List),Repository的方法只是直接调用DAO,没有挂起逻辑,所以IDE会提示suspend无用。但这其实不符合Room的最佳实践:Room的同步查询会阻塞当前线程,虽然你现在在Dispatchers.IO里调用没问题,但手动管理线程容易出错,Room本身推荐用挂起函数来做数据库操作,它会自动把查询调度到后台线程。
解决步骤
1. 彻底清理编译缓存
先执行IDE的「Clean Project」,再执行「Rebuild Project」,把旧的编译产物全部清掉,确保ViewModel和Repository的方法签名完全对齐。
2. 改用Room推荐的挂起函数查询
把DAO的查询改成挂起函数,这样Room会自动处理线程,代码更安全:
@Dao interface WorkoutDao { @Query("SELECT * From WorkoutListTest") suspend fun getWorkoutList() : List<WorkoutListTest> }
对应更新Repository的方法:
class WorkoutListRepository(private val dao: WorkoutDao) { suspend fun getWorkoutList() : List<WorkoutListTest> { return dao.getWorkoutList() } }
这样IDE就不会再提示suspend无用,同时也避免了手动线程调度的潜在问题。
3. 补全ViewModel的数据更新逻辑
你当前的ViewModel调用repository.getWorkoutList()后,没有把结果赋值给_list,需要补充这部分代码才能让Fragment观察到数据变化:
fun getList() { viewModelScope.launch(Dispatchers.IO) { val workoutList = repository.getWorkoutList() // 根据你的WorkoutListTest实体类,转换为String列表 val stringList = workoutList.map { it.yourPropertyName } _list.postValue(stringList) } }
内容的提问来源于stack exchange,提问作者ybybyb

