如何让RecyclerView等待Retrofit请求完成后再初始化?
正确解决RecyclerView等待Retrofit请求完成的方案
你的问题核心在于当前的getStaff()函数虽然标记为suspend,但内部用了Retrofit的enqueue()异步回调,导致协程根本不会等待请求完成就继续执行后续代码,所以你才需要用delay()这种不靠谱的方式来“凑时间”。下面给你两种合理的解决方案,从基础修复到更符合MVVM架构的优化:
方案一:修复协程与Retrofit的配合(基础版)
首先要改造ViewModel里的getStaff(),让它真正利用协程的挂起特性等待请求完成:
1. 改造Retrofit接口
确保你的API接口定义为suspend函数(Retrofit 2.6.0+支持这个特性):
interface HarryPotterApi { @GET("api/characters/staff") // 替换成你实际的接口路径 suspend fun staff(): List<YourCharacterModel> // 直接返回数据实体,而非Call对象 }
2. 重写ViewModel的getStaff()
去掉enqueue()回调,直接在协程中调用suspend接口方法,这样函数会挂起直到请求完成:
// 建议在ViewModel中使用viewModelScope,避免内存泄漏 fun getStaff() = viewModelScope.launch { val retrofit = Retrofit.Builder() .baseUrl("http://hp-api.herokuapp.com/") .addConverterFactory(GsonConverterFactory.create()) .build() val service = retrofit.create(HarryPotterApi::class.java) try { val characterData = service.staff() if (characterData.isNotEmpty()) { // 清空旧数据再添加新数据,避免重复 pictureList.clear() actorList.clear() characterList.clear() houseList.clear() pictureList.add(characterData[0].name) actorList.add(characterData[0].name) characterList.add(characterData[0].name) houseList.add(characterData[0].name) } } catch (e: IOException) { // 处理网络连接错误 e.printStackTrace() } catch (e: HttpException) { // 处理HTTP状态码错误(比如404、500) e.printStackTrace() } }
3. 改造Activity中的协程
使用和Activity生命周期绑定的lifecycleScope(替代GlobalScope,更安全),直接调用getStaff()后初始化RecyclerView:
lifecycleScope.launch { val txt = findViewById<TextView>(R.id.textbox) viewModel.getStaff() // 这里会自动挂起,直到请求完成 // 请求完成后自动回到主线程,直接初始化RV Log.d("coroutineScope", "#runs on ${Thread.currentThread().name}") createRV(viewModel.pictureList, viewModel.actorList, viewModel.characterList, viewModel.houseList) }
方案二:用LiveData实现MVVM架构(推荐版)
上面的基础版虽然能解决问题,但UI和数据的耦合还是有点高。更优雅的方式是让ViewModel用LiveData持有数据,Activity观察LiveData的变化,自动触发RecyclerView的初始化:
1. ViewModel中添加LiveData
// 先定义一个数据类封装所需的列表数据 data class StaffData( val pictureList: List<String>, val actorList: List<String>, val characterList: List<String>, val houseList: List<String> ) class YourViewModel : ViewModel() { // 私有可变LiveData,只在内部更新 private val _staffLiveData = MutableLiveData<StaffData>() // 对外暴露不可变的LiveData,供UI观察 val staffLiveData: LiveData<StaffData> = _staffLiveData fun getStaff() = viewModelScope.launch { val retrofit = Retrofit.Builder() .baseUrl("http://hp-api.herokuapp.com/") .addConverterFactory(GsonConverterFactory.create()) .build() val service = retrofit.create(HarryPotterApi::class.java) try { val characterData = service.staff() if (characterData.isNotEmpty()) { val data = StaffData( pictureList = listOf(characterData[0].name), actorList = listOf(characterData[0].name), characterList = listOf(characterData[0].name), houseList = listOf(characterData[0].name) ) // 用postValue确保在主线程更新LiveData _staffLiveData.postValue(data) } } catch (e: IOException) { e.printStackTrace() } catch (e: HttpException) { e.printStackTrace() } } }
2. Activity中观察LiveData
在onCreate()中观察LiveData,数据更新时自动初始化RecyclerView:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.your_activity_layout) val viewModel = ViewModelProvider(this)[YourViewModel::class.java] // 观察数据变化,自动触发RV初始化 viewModel.staffLiveData.observe(this) { staffData -> createRV(staffData.pictureList, staffData.actorList, staffData.characterList, staffData.houseList) } // 触发请求 lifecycleScope.launch { viewModel.getStaff() } }
关键注意点
- 永远不要用
GlobalScope:它的生命周期不受Activity/ViewModel控制,容易造成内存泄漏,改用lifecycleScope(Activity)或viewModelScope(ViewModel)。 - Retrofit版本:确保你的Retrofit依赖是2.6.0及以上,不然不支持suspend接口。
- 异常处理:不要忽略网络请求的异常,至少要打印日志方便排查问题。
- 数据清空:每次请求前清空旧数据,避免列表重复添加内容。
内容的提问来源于stack exchange,提问作者zain mahmood
相关产品推荐
相关产品推荐

