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

Android中Retrofit 2.3.0处理String与Object混合响应的方法

处理Retrofit中服务器返回纯字符串而非预期JSON对象的场景

遇到这种服务器在超时后返回纯字符串而非约定JSON的情况,确实挺头疼的。我给你几个实用的解决方案,你可以根据自己的需求选:

方法一:改用ResponseBody统一接收,手动控制解析逻辑

这个方法最直接,适合只有个别接口有这种问题的场景。你不用修改全局的Converter,只需要调整接口的返回类型,自己处理解析过程:

首先,把Retrofit接口的返回类型从Call<Person>改成Call<ResponseBody>:

public interface ApiService {
    @GET("person")
    Call<ResponseBody> getPerson();
}

然后在回调里先把响应转换成字符串,尝试解析成Person对象,捕获解析失败的异常:

apiService.getPerson().enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        if (response.isSuccessful()) {
            try {
                String responseContent = response.body().string();
                // 先尝试解析为Person对象
                Person person = new Gson().fromJson(responseContent, Person.class);
                // 解析成功,处理正常业务逻辑
                updateUIWithPerson(person);
            } catch (JsonSyntaxException e) {
                // 解析失败,说明返回的是纯字符串,这里根据服务器返回的内容判断是否为超时
                if ("超时".equals(responseContent.trim())) {
                    showToast("请求超时,请稍后重试");
                } else {
                    showToast("数据格式异常");
                }
            } catch (IOException e) {
                e.printStackTrace();
                showToast("读取响应失败");
            }
        } else {
            showToast("请求失败,错误码:" + response.code());
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        // 这里处理Retrofit层面的超时(比如连接超时、读取超时)
        if (t instanceof SocketTimeoutException) {
            showToast("请求超时,请稍后重试");
        } else {
            showToast("网络异常,请检查网络连接");
        }
    }
});

方法二:自定义GsonConverter,全局处理解析异常

如果你的多个接口都存在这种情况,那自定义一个全局的Converter会更高效,不用每个接口都重复写解析逻辑:

第一步:创建自定义的ResponseBodyConverter

继承Retrofit的Converter,重写convert方法,在里面捕获解析异常并判断是否为超时字符串:

public class CustomGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final TypeAdapter<T> typeAdapter;

    public CustomGsonResponseBodyConverter(Gson gson, TypeAdapter<T> typeAdapter) {
        this.gson = gson;
        this.typeAdapter = typeAdapter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        String responseStr = value.string();
        try {
            // 尝试正常解析为目标对象
            return typeAdapter.fromJson(responseStr);
        } catch (JsonSyntaxException e) {
            // 判断是否是服务器返回的超时字符串
            if ("超时".equals(responseStr.trim())) {
                // 抛出自定义的超时异常,方便后续捕获
                throw new ServerTimeoutException("服务器返回超时提示");
            }
            // 其他解析异常,重新抛出原异常
            throw e;
        } finally {
            value.close();
        }
    }

    // 自定义超时异常类
    public static class ServerTimeoutException extends IOException {
        public ServerTimeoutException(String message) {
            super(message);
        }
    }
}

第二步:创建自定义的GsonConverterFactory

用来替换Retrofit默认的GsonConverterFactory:

public class CustomGsonConverterFactory extends Converter.Factory {
    private final Gson gson;

    private CustomGsonConverterFactory(Gson gson) {
        if (gson == null) throw new NullPointerException("gson == null");
        this.gson = gson;
    }

    public static CustomGsonConverterFactory create() {
        return create(new Gson());
    }

    public static CustomGsonConverterFactory create(Gson gson) {
        return new CustomGsonConverterFactory(gson);
    }

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        TypeAdapter<?> typeAdapter = gson.getAdapter(TypeToken.get(type));
        return new CustomGsonResponseBodyConverter<>(gson, typeAdapter);
    }
}

第三步:替换Retrofit中的ConverterFactory

在构建Retrofit实例时,用自定义的工厂替换默认的GsonConverterFactory:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("你的基础URL")
        .addConverterFactory(CustomGsonConverterFactory.create())
        .build();

第四步:在回调中捕获自定义异常

这时候接口可以继续用Call<Person>,在回调里捕获自定义的超时异常:

apiService.getPerson().enqueue(new Callback<Person>() {
    @Override
    public void onResponse(Call<Person> call, Response<Person> response) {
        if (response.isSuccessful()) {
            updateUIWithPerson(response.body());
        } else {
            showToast("请求失败,错误码:" + response.code());
        }
    }

    @Override
    public void onFailure(Call<Person> call, Throwable t) {
        if (t instanceof CustomGsonResponseBodyConverter.ServerTimeoutException) {
            showToast("请求超时,请稍后重试");
        } else if (t instanceof SocketTimeoutException) {
            // 处理Retrofit层面的超时
            showToast("请求超时,请稍后重试");
        } else if (t instanceof JsonSyntaxException) {
            showToast("数据格式异常");
        } else {
            showToast("网络异常");
        }
    }
});

注意事项

  • 一定要和后端确认超时返回的具体字符串内容,比如是不是"超时"、"请求超时"还是其他,确保判断条件准确;
  • 两种方法都覆盖了服务器返回字符串超时和Retrofit自身的连接/读取超时两种场景,这样用户体验更完整;
  • 如果用了RxJava或者Coroutines,异常捕获的逻辑类似,只需要对应调整回调或者try-catch的位置即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 06:48:46