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

Android中Kotlin协程+Retrofit调用Spotify API请求顺序问题

问题解决:Spotify API 请求顺序错误导致空 Token

你的核心问题是**getSpotifyToken 函数虽然标记为 suspend,但内部用了 Retrofit 的 enqueue 异步回调,函数会直接返回,不会等待 Token 获取完成**,导致后续的 getAlbums 请求在 Token 赋值前就发起,传入了空值。

解决方案:让 Token 获取逻辑真正挂起

需要改造 getSpotifyToken,让它在 Token 回调完成前挂起协程,确保后续代码拿到有效 Token 再执行。有两种常用方式:


方式1:使用 Retrofit 协程扩展的 await()

先确保你的项目引入了 kotlinx-coroutines-retrofit 依赖,然后直接用 await() 替代 enqueue,将异步回调转为挂起函数:

private suspend fun getSpotifyToken(networkMusicRepository: NetworkMusicRepository): String {
    val response = networkMusicRepository.getToken().await()
    return if (response.isSuccessful) {
        val token = response.body()?.accessToken ?: throw IOException("Token 响应体为空")
        NetworkUiState.token = token
        token
    } else {
        throw HttpException(response)
    }
}

然后调整调用逻辑,直接获取返回的 Token 传入 getAlbums:

viewModelScope.launch {
    networkUiState = try {
        val networkMusicRepository = NetworkMusicRepository()
        // 等待 Token 获取完成后再继续
        val token = getSpotifyToken(networkMusicRepository)
        val listResult = networkMusicRepository.getAlbums(token)
        NetworkUiState.Success
    } catch (e: IOException) {
        Log.d("RESPONSE", e.message.toString())
        NetworkUiState.Error
    } catch (e: HttpException) {
        Log.d("RESPONSE", e.response()?.errorBody()?.string().toString())
        NetworkUiState.Error
    }
}

方式2:用 suspendCoroutine 封装回调

如果无法使用 Retrofit 协程扩展,可以手动用 suspendCoroutine 将回调转为挂起函数:

private suspend fun getSpotifyToken(networkMusicRepository: NetworkMusicRepository): String {
    return suspendCoroutine { continuation ->
        val responseCall = networkMusicRepository.getToken()
        responseCall.enqueue(object : Callback<TokenResponse> {
            override fun onResponse(call: Call<TokenResponse>, response: Response<TokenResponse>) {
                val responseBody = response.body()
                if (responseBody != null) {
                    val token = responseBody.accessToken
                    Log.i("RESPONSE", token)
                    NetworkUiState.token = token
                    // 回调完成,恢复协程并返回 Token
                    continuation.resume(token)
                } else {
                    continuation.resumeWithException(IOException("Token 响应体为空"))
                }
            }

            override fun onFailure(call: Call<TokenResponse>, t: Throwable) {
                // 请求失败,抛出异常
                continuation.resumeWithException(t)
            }
        })
    }
}

调用逻辑和方式1一致,确保 getAlbums 在 Token 拿到后执行。


关键原理

标记为 suspend 的函数本身不会自动挂起,只有内部调用了其他挂起函数(比如 await()、suspendCoroutine)时,才会真正暂停协程执行,直到异步任务完成。你原来的代码里用 enqueue 是纯异步回调,不会阻塞协程,所以后续代码会立刻执行。

内容的提问来源于stack exchange,提问作者Max

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 08:32:52