如何使用Retrofit2和RxAndroid处理条件响应?
解决Retrofit2+RxAndroid处理204成功/422错误响应的问题
这个问题我之前也碰到过,Completable确实在这种场景下有点局限——它只关注操作完成或失败,没法直接拿到422返回的错误响应体。不过有两种靠谱的解决方式,看你更倾向哪种:
方案一:保留Completable,在错误分支解析HttpException
Retrofit会把非2xx的响应包装成HttpException抛出,我们可以在onError回调里捕获这个异常,然后解析它携带的响应体:
首先,先定义一个对应422错误JSON的实体类(根据你的API返回结构调整):
public class ErrorResponse { private String detail; // 假设API返回的全局错误描述 private Map<String, List<String>> fieldErrors; // 如果是表单字段级验证错误 // Getter & Setter public String getDetail() { return detail; } public void setDetail(String detail) { this.detail = detail; } public Map<String, List<String>> getFieldErrors() { return fieldErrors; } public void setFieldErrors(Map<String, List<String>> fieldErrors) { this.fieldErrors = fieldErrors; } }
然后在订阅Completable的时候处理错误:
apiService.send(audit) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( () -> { // 请求成功(204)的逻辑 Toast.makeText(context, "提交成功", Toast.LENGTH_SHORT).show(); }, throwable -> { if (throwable instanceof HttpException) { HttpException httpException = (HttpException) throwable; if (httpException.code() == 422) { try { // 解析错误响应体 ErrorResponse errorResponse = new Gson().fromJson( httpException.response().errorBody().string(), ErrorResponse.class ); // 展示具体错误信息,优先全局描述,没有则用默认提示 String errorMsg = errorResponse.getDetail() != null ? errorResponse.getDetail() : "数据验证失败,请检查输入"; Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); Toast.makeText(context, "解析错误信息失败", Toast.LENGTH_SHORT).show(); } } else { // 处理其他HTTP错误,比如401未授权、500服务器错误等 Toast.makeText(context, "请求错误:" + httpException.code(), Toast.LENGTH_SHORT).show(); } } else { // 处理网络异常、超时等非HTTP响应错误 Toast.makeText(context, "网络异常,请稍后重试", Toast.LENGTH_SHORT).show(); } } );
⚠️ 注意:errorBody().string()只能调用一次,因为ResponseBody是流对象,调用后会自动关闭,不要重复调用。
方案二:更换返回类型为Single<Response>,更灵活处理状态码
如果想更清晰地区分“成功响应”“HTTP错误”和“网络错误”,可以把接口的返回类型从Completable改成Single<Response<Void>>,这样能手动处理所有状态码:
首先修改Retrofit接口方法:
@POST("/api/v1/audits/") Single<Response<Void>> send(@Body Audit audit);
然后订阅的时候处理:
apiService.send(audit) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( response -> { if (response.isSuccessful()) { // 204属于2xx范围,isSuccessful()会返回true Toast.makeText(context, "提交成功", Toast.LENGTH_SHORT).show(); } else { if (response.code() == 422) { try { ErrorResponse errorResponse = new Gson().fromJson( response.errorBody().string(), ErrorResponse.class ); Toast.makeText(context, errorResponse.getDetail(), Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); Toast.makeText(context, "解析错误信息失败", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(context, "请求错误:" + response.code(), Toast.LENGTH_SHORT).show(); } } }, throwable -> { // 这里只处理网络异常、连接超时等非HTTP响应的错误 Toast.makeText(context, "网络异常,请稍后重试", Toast.LENGTH_SHORT).show(); } );
这种方式的好处是把HTTP错误(比如422、404)和网络错误(比如无网、超时)分开处理,逻辑更清晰,也方便扩展处理其他状态码。
总结
- 如果想保持代码简洁,用方案一即可,只在错误分支处理422的情况;
- 如果需要更灵活的状态码处理,或者要区分不同类型的错误,方案二更合适。
内容的提问来源于stack exchange,提问作者László Soós
相关产品推荐
相关产品推荐

