Retrofit: java.lang.IllegalStateException: closed Retrofit:java.lang.IllegalStateException:closed

37

我正在使用两种拦截器,一种是HttpLoggingInterceptor,另一种是我自定义的AuthorizationInterceptor

我正在使用以下更新版本的retrofit库:

def retrofit_version = "2.7.2"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation 'com.squareup.okhttp3:okhttp:4.4.0'

以下是代码

private fun makeOkHttpClient(): OkHttpClient {
        val logger = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
        return OkHttpClient.Builder()
            .addInterceptor(AuthorizationInterceptor(context)) <---- To put Authorization Barrier
            .addInterceptor(logger) <---- To log Http request and response
            .followRedirects(false)
            .connectTimeout(50, TimeUnit.SECONDS)
            .readTimeout(50, TimeUnit.SECONDS)
            .writeTimeout(50, TimeUnit.SECONDS)
            .build()
    }

当我尝试执行名为SynchronizationManager.kt的文件中的以下代码时,它会出现错误。

var rulesResourcesServices = RetrofitInstance(context).buildService(RulesResourcesServices::class.java)
val response = rulesResourcesServices.getConfigFile(file).execute() <---In this line I am getting an exception... (which is at SynchronizationManager.kt:185)               

我的 RulesResourcesServices 类在这里

经过调试后,我发现当调用以下函数时,我会收到一个异常

@GET("users/me/configfile")
    fun getConfigFile(@Query("type") type: String): Call<ResponseBody>

我遇到了以下错误

java.lang.IllegalStateException: closed
at okio.RealBufferedSource.read(RealBufferedSource.kt:184)
at okio.ForwardingSource.read(ForwardingSource.kt:29)
at retrofit2.OkHttpCall$ExceptionCatchingResponseBody$1.read(OkHttpCall.java:288)
at okio.RealBufferedSource.readAll(RealBufferedSource.kt:293)
at retrofit2.Utils.buffer(Utils.java:316)<------- ANDROID IS HIGH-LIGHTING
at retrofit2.BuiltInConverters$BufferingResponseBodyConverter.convert(BuiltInConverters.java:103)
at retrofit2.BuiltInConverters$BufferingResponseBodyConverter.convert(BuiltInConverters.java:96)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:188)
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall.execute(DefaultCallAdapterFactory.java:97)
at android.onetap.SynchronizationManager.downloadFile(SynchronizationManager.kt:185)
at android.base.repository.LoginRepository.downloadConfigFilesAndLocalLogin(LoginRepository.kt:349)
at android.base.repository.LoginRepository.access$downloadConfigFilesAndLocalLogin(LoginRepository.kt:48)
at android.base.repository.LoginRepository$loginTask$2.onSRPLoginComplete(LoginRepository.kt:210)
at android.base.repository.LoginRepository$performSyncLogin$srpLogin$1$1.onSRPLogin(LoginRepository.kt:478)
at android.srp.SRPManager$SRPLoginOperation$execute$1.invokeSuspend(SRPManager.kt:323)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)

以下是一张截图,在截图中您可以看到,我得到了文件的输出,但不知道为什么会抛出异常。

在此输入图片描述

检查了Retrofit的Utils类

https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/Utils.java

static ResponseBody buffer(final ResponseBody body) throws IOException {
    Buffer buffer = new Buffer();
    body.source().readAll(buffer); <-This line throws an error.
    return ResponseBody.create(body.contentType(), body.contentLength(), buffer);
  }

更新

使用enqueue方法可以正常工作。

response.enqueue(object : Callback<ResponseBody?> {

override fun onResponse(call: Call<ResponseBody?>, response: retrofit2.Response<ResponseBody?>) { 
 }
})

我已经向Retrofit团队发布了相同的问题,让我们拭目以待。

https://github.com/square/retrofit/issues/3336


myapp.base.AuthenticationManager.checkEnterpriseAccountStatus(AuthenticationManager.kt:492)听起来与原因相关。 - EpicPandaForce
我已经更新了问题,包括Retrofit的错误行,从哪里抛出错误,请检查。 - Siddhpura Amit
似乎输入源由于某些原因已关闭,您确定只调用了一次execute吗?看起来您正尝试从同一个ResponseBody中读取两次,而这在Retrofit中是不被允许的。 - Nicola Gallazzi
你如何找出“IllegalStateException: closed”是由于Inputsource关闭引起的? - Siddhpura Amit
随着更新后的堆栈溢出,我现在对at android.onetap.SynchronizationManager.downloadFile(SynchronizationManager.kt:185)很好奇。 - EpicPandaForce
显示剩余5条评论
4个回答

89

感谢JakeWharton (https://github.com/square/retrofit/issues/3336),我能够得到解决方案。 实际上,在我的自定义拦截器中,我是通过以下代码读取响应的


Response.body().string()

上面的代码帮助我找出是否有错误,如果有错误,它能告诉我是什么类型的错误...

如果是AUTH_ERROR,我需要生成新的令牌并将其附加到请求头中。

根据Retrofit文档,如果我们调用以下任何一种方法,则响应将被关闭,这意味着它不能被常规的Retrofit内部消费。

Response.close()
Response.body().close()
Response.body().source().close()
Response.body().charStream().close()
Response.body().byteStream().close()
Response.body().bytes()
Response.body().string()

因此,为了读取数据,我将使用

 response.peekBody(2048).string()

代替

 response.body().string(), 

所以它不会关闭响应。

以下是最终代码

 val response = chain.proceed(request)
            val body = response.peekBody(Long.MAX_VALUE).string()//<---- Change
            try {
                if (response.isSuccessful) {
                    if (body.contains("status")) {
                        val jsonObject = JSONObject(body)
                        val status = jsonObject.optInt("status")
                        Timber.d("Status = $status")
                        if (status != null && status == 0) {
                            val errorCode = jsonObject.getJSONObject("data").optString("error_code")
                            if (errorCode != null) {
                                addRefreshTokenToRequest(request)
                                return chain.proceed(request)
                            }
                        }
                    } else {
                        Timber.d("Body is not containing status, might be not valid GSON")
                    }
                }
                Timber.d("End")
                
            } catch (e: Exception) {
                e.printStackTrace()
                Timber.d("Error")
            }
            return response

我已经在我的应用程序中使用了Retrofit与Retrofit响应。但是我遇到了需要关闭先前调用的问题,请确保使用response.close()进行关闭。如果您有任何解决方案,请在此处告诉我 https://stackoverflow.com/questions/64333385/cannot-make-a-new-request-because-the-previous-response-is-still-open-please-ca - sejn
4
请注意这种方法。如果您的响应长度超过2048字节,Retrofit将抛出 java.io.EOFException - Saman Sattari
如果由于2048字节的错误而出现问题,您可以增加值,我必须这样做,因为我知道从我的Web服务响应中永远不会有超过2048字节的值。 - Siddhpura Amit
1
@SamanSattari JakeWharton在这个Github评论中提到要使用.peekBody(Long.MAX_VALUE) - Rumit Patel
谢谢SamanSattari和Rumit,我已经更新了我的答案。 - Siddhpura Amit

5
扩展@Siddhpura Amit的答案: 如果您不知道要传递到peak方法的字节,则仍然可以使用所有方法,但只需创建新的Response对象即可。
拦截器内部:
okhttp3.Response response = chain.proceed(request);
String responseBodyString = response.body().string();

//Do whatever you want with the above string

ResponseBody body = ResponseBody.create(response.body().contentType(), responseBodyString);
return response.newBuilder().body(body).build();

2
对于 Kotlin,ResponseBody.create 已被弃用。取而代之,请使用:responseBodyString.toResponseBody(response.body.contentType()) - John Santos

1
也许你在你的AuthorizationInterceptor中这样关闭了你的响应
override fun intercept(chain: Interceptor.Chain): Response {
   ...
   val response = chain.proceed(builder.build())
   response.close()
   ...
}

0
在我的情况下,我使用了 Response.body().string() 两次,但实际上只允许使用一次。然后问题就解决了。

你的回答是一个变通方法。 也许最好是找出问题的具体原因,并提出一个更加技术性的解决方案。 - rfellons

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接