Retrofit2并发请求异常:RecyclerView无法正常填充数据
排查与解决思路
这种并发请求导致其中一个UI组件不更新的问题我之前也碰到过,大概率是这几个原因之一,咱们一步步来排查和解决:
1. 检查Retrofit Call实例是否被复用
Retrofit的Call对象是一次性的,如果你在两个方法里复用了同一个Call实例,同时执行的话第二个请求会直接失败。确保每次调用fetchPremiumStudios()和fetchDistricts()时,都是创建新的Call对象:
错误示例(复用Call实例):
// 错误:同一个Call不能被多次执行 private val premiumStudiosCall = apiService.getPremiumStudios() fun fetchPremiumStudios() { premiumStudiosCall.enqueue(...) }
正确写法(每次新建Call):
fun fetchPremiumStudios() { val call = apiService.getPremiumStudios() // 每次调用都创建新的Call实例 call.enqueue(object : Callback<List<Studio>> { override fun onResponse(call: Call<List<Studio>>, response: Response<List<Studio>>) { // 处理数据并更新RecyclerView } override fun onFailure(call: Call<List<Studio>>, t: Throwable) { // 一定要打印错误信息!这里很容易忽略 t.printStackTrace() } }) }
2. 确保RecyclerView的更新在主线程执行
虽然Retrofit默认的Callback是在主线程回调,但如果你自定义了Retrofit的Executor,可能会导致回调在子线程执行。此时必须切换到主线程更新UI:
override fun onResponse(call: Call<List<Studio>>, response: Response<List<Studio>>) { if (response.isSuccessful) { val studios = response.body() ?: emptyList() // 强制在主线程更新RecyclerView requireActivity().runOnUiThread { yourAdapter.submitList(studios) // 如果用的是普通Adapter,记得调用notifyDataSetChanged() yourAdapter.notifyDataSetChanged() } } }
3. 检查是否有回调冲突或数据被覆盖
仔细查看fetchDistricts()的回调逻辑,有没有不小心操作到RecyclerView的Adapter——比如误重置了Adapter实例、清空了数据:
// 错误示例:fetchDistricts里误操作了RecyclerView的Adapter fun fetchDistricts() { apiService.getDistricts().enqueue(object : Callback<List<District>> { override fun onResponse(call: Call<List<District>>, response: Response<List<District>>) { // ...填充Spinner的逻辑 // 不小心写了这句,直接覆盖了RecyclerView的Adapter recyclerView.adapter = someOtherAdapter } }) }
确保两个请求的回调只操作各自对应的UI组件,不要交叉影响。
4. 确认fetchPremiumStudios是否请求失败
同时调用时,这个请求可能因为网络超时、接口返回错误等原因失败,但你没处理onFailure回调。一定要在onFailure里打印异常日志:
override fun onFailure(call: Call<List<Studio>>, t: Throwable) { Log.e("PremiumStudios", "请求失败: ${t.message}", t) // 也可以在这里给用户显示错误提示 }
通过Logcat查看错误信息,就能知道为什么数据为空了。
5. 检查RecyclerView的初始化时机
确保在调用fetchPremiumStudios()之前,RecyclerView和它的Adapter已经完成初始化。比如在onViewCreated()里先初始化UI,再发起请求:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 先初始化RecyclerView和Adapter val adapter = PremiumStudiosAdapter() recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(context) // 再同时发起两个请求 fetchDistricts() fetchPremiumStudios() }
6. 推荐:用Coroutines替代Callback(更简洁可靠)
如果你的项目已经用了Kotlin协程,改用协程处理并发请求会避免很多回调冲突的问题,代码也更易读:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 初始化RecyclerView... lifecycleScope.launch { // 并发执行两个请求 val districtsDeferred = async { apiService.getDistricts() } val studiosDeferred = async { apiService.getPremiumStudios() } try { val districts = districtsDeferred.await() val studios = studiosDeferred.await() // 更新Spinner districtSpinner.adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, districts) // 更新RecyclerView yourAdapter.submitList(studios) } catch (e: Exception) { Log.e("MainFragment", "请求失败: ${e.message}", e) } } }
内容的提问来源于stack exchange,提问作者Ganesh T
相关产品推荐
相关产品推荐

