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

Retrofit客户端遇Transfer-Encoding: chunked响应报错的解决方法

解决Retrofit/OkHttp处理Chunked响应时的ProtocolException问题

我最近在开发一款Android电影查询应用,通过OMDb API(接口地址:http://www.omdbapi.com/?s=star&apikey=d497e644)获取电影数据,用Retrofit编写了客户端接口:

public interface OmdbApi { 
    @Streaming 
    @GET("./") 
    @Headers({"Cache-control: no-cache"}) 
    Call<Search> search(@Query("s") String search, @Query("apikey") String apiKey); 

    @Streaming 
    @GET("./") 
    @Headers({"Cache-control: no-cache"}) 
    Call<FilmDetail> getFilm(@Query("i") String uid, @Query("apikey") String apiKey); 
}

运行应用时,Logcat能看到API返回了正常的响应头(包含Transfer-Encoding: chunked),但同时抛出了以下错误:

java.net.ProtocolException: unexpected end of stream at okhttp3.internal.http1.Http1Codec$ChunkedSource.read(Http1Codec.java:455) 
at okio.RealBufferedSource.read(RealBufferedSource.java:47) 
okio.RealBufferedSource.exhausted(RealBufferedSource.java:57) 
okio.InflaterSource.refill(InflaterSource.java:102) 
okio.InflaterSource.read(InflaterSource.java:62) 
okio.GzipSource.read(GzipSource.java:80) 
okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:237) 
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147) 
okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121) 
okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:200) 
at okhttp3.RealCall.execute(RealCall.java:77) 
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180) 
retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:91) 
com.abubusoft.filmfinder.service.repository.FilmRepository.lambda$findFilm$0$FilmRepository(FilmRepository.java:18) 
com.abubusoft.filmfinder.service.repository.FilmRepository$$Lambda$0.run(Unknown Source:20) 
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
at java.lang.Thread.run(Thread.java:764)

排查后发现问题和响应的Transfer-Encoding: chunked编码有关,下面分享几个有效的解决办法:

1. 调整HttpLoggingInterceptor的日志级别

从错误栈能明确看到,问题出在HttpLoggingInterceptor的拦截流程里。当日志级别设为BODY时,拦截器会读取整个响应流来输出日志,而在chunked编码+gzip压缩的场景下,这个操作会直接耗尽响应流,导致后续Retrofit解析数据时无内容可用。

解决办法是把日志级别调低,比如改成HEADERS或BASIC:

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
// 避免使用BODY级别,改用HEADERS只打印请求/响应头
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(loggingInterceptor)
    .build();

2. 移除接口中的@Streaming注解

如果你的响应不是超大文件(比如电影详情这种小体积JSON),完全不需要使用@Streaming注解。这个注解会让Retrofit以流的方式处理响应体,一旦日志拦截器读取了流,后续的解析操作就会因为流耗尽而失败。

修改后的接口代码:

public interface OmdbApi { 
    @GET("./") 
    @Headers({"Cache-control: no-cache"}) 
    Call<Search> search(@Query("s") String search, @Query("apikey") String apiKey); 

    @GET("./") 
    @Headers({"Cache-control: no-cache"}) 
    Call<FilmDetail> getFilm(@Query("i") String uid, @Query("apikey") String apiKey); 
}

3. 检查OkHttpClient的gzip配置

OkHttp默认会自动处理gzip压缩的响应,不需要手动添加额外的gzip拦截器。如果你的客户端配置了重复的gzip处理逻辑,可能会导致流处理异常。确保OkHttpClient的拦截器列表里没有重复的gzip相关拦截器。

4. 自定义日志拦截器(如果需要BODY级日志)

如果你确实需要打印完整的响应体日志,可以自定义拦截器,使用response.peekBody()来获取响应体的副本进行日志输出,这样不会消耗原始的响应流:

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(chain -> {
        Request request = chain.request();
        Log.d("OkHttp", "Request: " + request.url());
        
        Response response = chain.proceed(request);
        // 复制响应体,避免消耗原始流
        ResponseBody responseBody = response.peekBody(Long.MAX_VALUE);
        Log.d("OkHttp", "Response Body: " + responseBody.string());
        
        return response;
    })
    .build();

以上几个方案中,最直接有效的是前两个——调整日志级别或者移除@Streaming注解,大部分情况下都能解决这个chunked响应导致的ProtocolException问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:51:56