You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 22:52:29