Kotlin协程中Suspend未等待HTTP请求响应问题求助
问题原因分析
你遇到的核心问题是suspend函数没有真正挂起等待网络请求结果,直接返回了初始值。具体来说:
在api_service的login方法里,你用了Retrofit的enqueue异步回调来处理网络请求,但这个方法是异步执行的——你的login函数会在回调触发前就直接返回responseFromApi的初始值null,而协程会立刻执行setTextOnMainThread(result.toString()),这时候API的响应还没回来,所以result自然是空的。
解决方案
我们需要利用Retrofit对协程的原生支持,让login函数真正挂起直到网络请求完成,再返回结果。下面是分步修改:
1. 改造JsonPlaceholderApi接口
将login方法改为返回suspend函数,直接返回Response<ResponseBody>,去掉Call包装:
interface JsonPlaceholderApi { @POST("auth/login") suspend fun login(@Body model: LoginModel): Response<ResponseBody> }
2. 重构api_service的login方法
去掉enqueue回调,直接在suspend函数里调用接口方法并等待结果:
class api_service { private val jsonPlaceholderApi: JsonPlaceholderApi init { val retrofit = Retrofit.Builder() .baseUrl("http://192.168.1.8:5000/api/") .addConverterFactory(GsonConverterFactory.create()) .build() jsonPlaceholderApi = retrofit.create(JsonPlaceholderApi::class.java) } suspend fun login(model: LoginModel): String? { return try { val response = jsonPlaceholderApi.login(model) if (response.isSuccessful) { val token = response.body()?.string() token?.split(':')?.get(1) // 提取token部分 } else { "请求失败:${response.code()}" } } catch (t: Throwable) { t.message // 返回错误信息 } } }
这里做了几个优化:
- 用
init块代替构造函数,代码更简洁 - 用
try-catch捕获网络异常,避免崩溃 - 直接返回处理后的结果,不用依赖回调赋值
3. 优化LoginFragment的协程使用
不要直接用CoroutineScope(IO),推荐用lifecycleScope(针对Fragment),它会自动跟随Fragment的生命周期销毁,避免内存泄漏:
view.next_button.setOnClickListener { val usernameInput = username_edit_text.text.orEmpty() val passwordInput = password_edit_text.text.orEmpty() if (!isTextFieldCorrect(passwordInput)) { password_text_input.error = getString(R.string.shr_error_password) username_text_input.error = getString(R.string.shr_error_username) } else { next_button.isEnabled = false password_text_input.error = null username_text_input.error = null val model = LoginModel(usernameInput.take(5), passwordInput.take(5)) val apiService = api_service() // 使用lifecycleScope代替CoroutineScope(IO) lifecycleScope.launch { val result = apiService.login(model) // 直接在lifecycleScope里切换到主线程更新UI withContext(Dispatchers.Main) { textView_status.text = result ?: "未知错误" next_button.isEnabled = true // 请求结束后恢复按钮状态 } } } }
这里还做了细节优化:
- 用
orEmpty()避免!!强制非空,降低空指针风险 - 请求结束后恢复按钮可点击状态,防止重复触发请求
额外建议
- 可以把
api_service改成单例,避免每次请求都创建新的Retrofit实例 - 考虑用ViewModel来托管网络请求逻辑,让Fragment只负责UI展示,代码更解耦
- 对token的解析可以更严谨,比如判断split后的数组长度是否足够,避免数组越界
内容的提问来源于stack exchange,提问作者Ibanez1408
相关产品推荐
相关产品推荐

