Retrofit 动态支持多数据格式:自定义 ConverterFactory 同时解析 JSON 与 XML
在 Retrofit 中,addConverterFactory() 方法用于配置数据转换器,从而将 HTTP 响应体自动反序列化为 Java 对象。针对常见的 JSON 和 XML 数据格式,官方分别提供了 GsonConverterFactory 和 SimpleXmlConverterFactory。
依赖配置与已知问题
引入对应的转换器依赖:
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-simplexml:2.9.0'
注意:SimpleXmlConverterFactory 官方已标记为废弃,建议使用 converter-jaxb。但在实际 Android 项目中,JAXB 转换器经常会出现兼容性问题,因此许多项目仍在使用 SimpleXML 或迁移至其他 XML 解析库。本文以 SimpleXML 为例进行演示。
多转换器冲突现象
Retrofit 的转换器链采用"责任链"模式,但在处理 JSON 和 XML 时,由于底层解析器的严格校验机制,通常无法简单地叠加使用。例如:
Retrofit client = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(SimpleXmlConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
在上述配置中,XML 转换器被优先添加。当请求返回 JSON 数据时,XML 解析器会尝试解析并抛出异常,导致请求失败,而不会将解析权移交后续的 Gson 转换器。反之,若将 Gson 放在前面,解析 XML 时同样会崩溃。简而言之,只有排列在首位的转换器能够生效。
解决方案:基于注解的动态路由
为了让同一个 Retrofit 实例能够根据具体接口智能选择解析器,我们可以通过自定义注解来标记接口的预期返回格式,并编写一个聚合型的 Converter.Factory 进行路由分发。
1. 定义数据格式注解
创建一个用于标记方法响应类型的注解:
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 标记接口响应的数据格式
*/
@Target(METHOD)
@Retention(RUNTIME)
public @interface ResponseDataType {
String FORMAT_JSON = "json";
String FORMAT_XML = "xml";
String value() default FORMAT_JSON;
}
2. 实现自定义 ConverterFactory
构建一个内部持有 JSON 和 XML 转换器实例的工厂类,在 responseBodyConverter 方法中读取注解并分发请求:
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
public class DynamicFormatConverterFactory extends Converter.Factory {
private final Converter.Factory jsonDelegate = GsonConverterFactory.create();
private final Converter.Factory xmlDelegate = SimpleXmlConverterFactory.create();
public static DynamicFormatConverterFactory create() {
return new DynamicFormatConverterFactory();
}
@Override
public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
for (Annotation annotation : annotations) {
if (annotation instanceof ResponseDataType) {
String format = ((ResponseDataType) annotation).value();
if (ResponseDataType.FORMAT_XML.equals(format)) {
return xmlDelegate.responseBodyConverter(type, annotations, retrofit);
} else {
return jsonDelegate.responseBodyConverter(type, annotations, retrofit);
}
}
}
// 默认回退到 JSON 解析
return jsonDelegate.responseBodyConverter(type, annotations, retrofit);
}
}
3. 接口定义与客户端构建
在 API 接口中使用自定义注解,并配置 Retrofit 实例:
import io.reactivex.Observable;
import retrofit2.http.GET;
public interface MultiFormatService {
@GET("api/v1/user/profile")
@ResponseDataType(ResponseDataType.FORMAT_JSON)
Observable<UserProfile> fetchJsonProfile();
@GET("api/v1/config/settings")
@ResponseDataType(ResponseDataType.FORMAT_XML)
Observable<AppSettings> fetchXmlSettings();
}
// 构建 Retrofit 实例
Retrofit retrofitClient = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(DynamicFormatConverterFactory.create())
.build();
// 发起请求
MultiFormatService service = retrofitClient.create(MultiFormatService.class);
service.fetchJsonProfile()
.subscribe(profile -> {
// 处理 JSON 解析后的对象
});
service.fetchXmlSettings()
.subscribe(settings -> {
// 处理 XML 解析后的对象
});