天天看點

Retrofit 2.0 自定義Converter

requestBodyConverter 不執行的解決辦法:

參數要使用@Body這種形式,否則 request 方法會不起作用。

在Retrofit中,無論是發送資料和接收資料,都是通過OKHttp的RequestBody和ResponseBody來實作的。在實際項目中,有時候原始的RequestBody或是ResponseBody并不能滿足我們的需求(如接口加密),就需要對它進行轉換。

在Retrofit通過build()方法獲得執行個體時,可以添加多個ConverterFactory,但要注意的是,添加的順序是有影響的。如下代碼:

.addConverterFactory(GsonConverterFactory.create())      

看下源碼:

private final List<Converter.Factory> converterFactories;

public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }      

按照retrofit的邏輯,是從前往後進行比對,如果比對上,就忽略後面的,直接使用。

int start = converterFactories.indexOf(skipPast) + 1;
    for (int i = start, count = converterFactories.size(); i < count; i++) {
      Converter.Factory factory = converterFactories.get(i);
      Converter<?, RequestBody> converter =
          factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
      if (converter != null) {
        //noinspection unchecked
        return      

從上面的源碼中可以看到,當factory.requestBodyConverter傳回空時,表示沒有比對上,可使用下一個factory.

是以,當我們自定義converter的時候,需要進行條件判斷,符合我們一定規則的才能使用。

Retrofit官方給了以下幾個常用的轉換庫

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

Type

我們以建立protobuff為例。

Retrofit已經為我們提供了自定義ConverterFactory的接口,我們隻需要實作它給的接口即可。

public final class ProtoConverterFactory extends Converter.Factory
  public static ProtoConverterFactory create() {
    return new ProtoConverterFactory();
  }

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
      //進行條件判斷,如果傳進來的Type不是class,則比對失敗
    if (!(type instanceof Class<?>)) {
      return null;
    }
      //進行條件判斷,如果傳進來的Type不是MessageLite的實作類,則也比對失敗
    Class<?> c = (Class<?>) type;
    if (!MessageLite.class.isAssignableFrom(c)) {
      return null;
    }

    Parser<MessageLite> parser;
    try {
      Field field = c.getDeclaredField("PARSER");
      //noinspection unchecked
      parser = (Parser<MessageLite>) field.get(null);
    } catch (NoSuchFieldException | IllegalAccessException e) {
      throw new IllegalArgumentException(
          "Found a protobuf message but " + c.getName() + " had no PARSER field.");
    }
    //傳回一個實作了Converter的類,
    return new ProtoResponseBodyConverter<>(parser);
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      //進行條件判斷,如果傳進來的Type不是class,則比對失敗
    if (!(type instanceof Class<?>)) {
      return null;
    }
    //進行條件判斷,如果傳進來的Type不是MessageLite的實作類,則也比對失敗
    if (!MessageLite.class.isAssignableFrom((Class<?>) type)) {
      return null;
    }
    return new      

注意在Convert的兩個泛型中,前一個類型是傳進來的對象類型,後一個類型是轉換後的對象類型。

final class ProtoRequestBodyConverter<T extends MessageLite> implements Converter<T, RequestBody> {
  private static final MediaType MEDIA_TYPE = MediaType.parse("application/x-protobuf");

  @Override public RequestBody convert(T value) throws IOException {
    byte[] bytes = value.toByteArray();
    return      
final class ProtoResponseBodyConverter<T extends MessageLite>
    implements Converter<ResponseBody, T> {
  private final Parser<T> parser;

  ProtoResponseBodyConverter(Parser<T> parser) {
    this.parser = parser;
  }

  @Override public T convert(ResponseBody value) throws IOException {
    try {
      return parser.parseFrom(value.byteStream());
    } catch (InvalidProtocolBufferException e) {
      throw new RuntimeException(e); // Despite extending IOException, this is data mismatch.
    } finally      

Annotation

在有一些情況,僅僅通過type來進行判斷,資訊是不夠的,還需要額外的參數。這時我們就可以利用後面兩個參數來進行。

我們先看requestBodyConverter的函數簽名。

public Converter<?, RequestBody> requestBodyConverter(Type type,      

顯然,parameterAnnoatations是指在定義接口的參數上的注解。如下面的MyType:

@GET("applist/mini-appcenter/")
    Call<MiniAppCenterPojo> getMiniApp(@Query("offsets") @TypeString String      

定義在方法上的注釋就是methodAnnotations

@TypeString
    @GET("applist/mini-appcenter/")
    Call<MiniAppCenterPojo> getMiniApp(@Query("offsets");      

我們就可以通過對這些注解的判斷來進行自定義Converter的比對。

@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface TypeString{      
public class StringConverterFactory extends Converter.Factory

    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                            Retrofit retrofit) {
        if (!(type instanceof Class<?>)) {
            return null;
        }

        for( Annotation annotation :annotations) {
            if( annotation instanceof TypeString) {
                return new StringResponseConverter();
            }
        }

        return null;
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
                                                          Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        if (!(type instanceof Class<?>)) {
            return null;
        }
        for( Annotation annotation :parameterAnnotations) {
            if( annotation instanceof TypeString) {
                return new StringRequestConverter();
            }
        }
        return null;
    }

    public static class StringResponseConverter implements Converter<ResponseBody, String> {

        @Override
        public String convert(ResponseBody value) throws IOException {
            return value.string();
        }
    }

    public static class StringRequestConverter implements Converter<String, RequestBody> {
        @Override
        public RequestBody convert(String value) throws IOException {
            return RequestBody.create(MediaType.parse("application/octet-stream"), value);
        }
    }
}      

下面再看一個基于Gson的自定義ConverterFactory。

如果我們對安全性要求比較高,或者編碼不太一樣的話,預設的GsonConverterFactory就不行了,我們就需要自定義ConverterFactory。

代碼如下:

public final class DecodeConverterFactory extends Converter.Factory

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

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

    private final Gson gson;

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

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

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] annotations,Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new      

然後我們需要自定義responseBodyConverter和requestBodyConverter,這裡我們發送請求的時候不需要加密,接收請求的時候需要解密,具體代碼如下:

public class DecodeResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final TypeAdapter<T> adapter;

    DecodeResponseBodyConverter(TypeAdapter<T> adapter) {
        this.adapter = adapter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        //解密字元串
        return adapter.fromJson(EncryptUtils.decode(value.string()));
    }
}

public class DecodeRequestBodyConverter<T> implements Converter<T, RequestBody> {
    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
    private static final Charset UTF_8 = Charset.forName("UTF-8");

    private final Gson gson;
    private final TypeAdapter<T> adapter;
    DecodeRequestBodyConverter(Gson gson,TypeAdapter<T> adapter){
        this.gson = gson;
        this.adapter = adapter;
    }
    @Override
    public RequestBody convert(T value) throws IOException {
        Buffer buffer = new Buffer();
        Writer writer = new OutputStreamWriter(buffer.outputStream(),UTF_8);
        JsonWriter jsonWriter = gson.newJsonWriter(writer);
        adapter.write(jsonWriter,value);
        jsonWriter.flush();
        return      

需要注意的是:adapter.fromJson(EncryptUtils.decode(value.string())) 中EncryptUtils.decode(value.string())傳回類型必須是json字元串,否則會報錯。

參考文章:

​​​http://caiyao.name/2016/01/13/Retrofit%E4%BD%BF%E7%94%A8%E4%B9%8B%E8%87%AA%E5%AE%9A%E4%B9%89Converter/​​

可能遇到的問題:

​​​when required parameters have string and file together, retrofit 2.2.0 do not work ​​

requestBodyConverter 不執行的解決辦法:

參數要使用@Body這種形式,否則 request 方法會不起作用。

例如:

@POST("/index/index/sms_code")
Observable<BaseResopnse> sms_code(@Body String      
/**
     * Returns a {@link Converter} for converting {@code type} to an HTTP request body, or null if
     * {@code type} cannot be handled by this factory. This is used to create converters for types
     * specified by {@link Body @Body}, {@link Part @Part}, and {@link PartMap @PartMap}
     * values.
     */
    public @Nullable Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }