Retrofit详解:基本API + 自定义Converter

本文是围绕Retrofit2.1版本来讲的,一些老版本API和新版本不一样了,就不讲了,与时俱进。
Retrofit是Square公司开发的,大神是JakeWharton。官网对其描述是:

a type-safe REST client for Android and Java.

你可以使用注解去描述一个HTTP请求、Url参数替换、查询参数。另外也支持文件上传等。

如何引用:

dependencies {  
    // Retrofit & OkHttp
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
}

第一个是Retrofit库,第二个是Gson库(用来将string转换成json的库),在retrofit中默认使用gson这个converter来转换response,后面会解释converter。

开始使用:
例如我现在要发出一个请求,url如下:

http://10.240.88.201:8080/repos/{owner}/contributors?loginname={name}

在后面路径中有{owner},这是一个替换位,这个值非固定,传不同的owner则返回不同的值。返回的是一个jsonArray,并且是一个get请求。在参数中,有一对键值对,{name}也是替换位,传入不同参数则返回不同值。
那看下retrofit怎么解决这个问题:

public class ServiceGenerator {

    public static final String API_BASE_URL = "http://10.240.88.201:8080/";

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .baseUrl(API_BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create());

    public static <S> S createService(Class<S> serviceClass) {
        Retrofit retrofit = builder.client(httpClient.build()).build();
        return retrofit.create(serviceClass);
    }
}
public interface RequestClient {

    @GET("/repos/{owner}/contributors")
    Call<List<Contributor>> contributors(
            @Path("owner") String owner,
            @Query("loginname") String loginname
    );

    class Contributor {
        String login;
        int contributions;
    }
}
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //发起网络请求
        RequestClient client = ServiceGenerator.createService(RequestClient.class);

        Call<List<Contributor>> call = client.contributors("company", "lxx");

        call.enqueue(new Callback<List<Contributor>>() {
            @Override
            public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
                List<Contributor> contributors = response.body();
                Toast.makeText(MainActivity.this, contributors.size() + "", Toast.LENGTH_LONG).show();
            }

            @Override
            public void onFailure(Call<List<Contributor>> call, Throwable t) {
                Log.e("tag", "------------onFailure:");
                t.printStackTrace();
            }
        });
    }
}

如果服务器返回的值为:

[
    {
        "login":"ssss",
        "contributions":1
    },
    {
        "login":"ssss",
        "contributions":2
    }
]

则在界面上会弹出toast,显示2。
现在来解释下,在ServiceGenerator中使用Builder创建一个新的REST client,并且基于API_BASE_URL。Retrofit其实是对OkHttp库的封装,简化里面参数的封装以及数据转换,所以在创建Retrofit的时候,还需要传入OkHttpClient。如对OkHTTP不熟悉的话,可以看OkHttp完全解析
和OkHttp不同的是,Retrofit需要定义一个借口,用来返回我们的Call对象,这个Call对象可不是OkHttp中的Call,而是定义在Retrofit中的Call。
这里我们定义的是RequestClient这个接口,在这个接口中定义了一个函数contributors。看下修饰该函数的注解,首先用了注解@GET来标识这是一个GET请求,当然你也可以使用@POST。在@GET括号中的是该请求的具体路径,如果在路径中出现{xxx}这样的格式,则说明该处为替换位,可以在函数参数中看到,有注解@Path(“owner”)来标识该处是用来替换的。
而@Query参数是用来标示参数键值对的。
在MainActivity中我们调用了该接口的contributor(“company”,”lxx”);,那么我们发出请求的url便是:

http://10.240.88.201:8080/repos/company/contributors?loginname=lxx

这样一看Retrofit是不是很方便,MainActivity在创建了Call之后,就会去请求,调用的方法和OkHttp的Call一样。其实我们仔细看Retrofit.Call和OkHttp.Call接口,其实是一样的,那为什么要搞两个呢?我猜想是和后面的切面编程有关系。
我们继续回到ServiceGenerator中的createService方法,在该方法中创建了一个retrofit后,调用了create(Class class)的方法,具体进去看看:

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

该方法前面是对该类进行基本检查,检查是否是接口,并且该接口不能继承自其它接口。然后就是调用了Proxy(动态代理,运用了切面编程的思想,不懂可以看如下两篇文章:Java的动态代理(dynamic proxy)深入理解Java Proxy机制
接下来再看下创建ServiceMethod,使用method初始化该ServiceMethod,method变量就是我们外面调用的contributors这个方法,而loadServiceMethod就是获取该method的注解、参数类型、参数的注解。
看代码:

  ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

    public Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }

所以我们外面定义的接口RequestClient只是用来获取这三样而已,里面不用方法则不用实现。而切面编程中,我们返回了一个实现Retrofit.Call接口的实例Retrofit.OkHttpCall。在Retrofit.OkHttpCall中封装了请求OkHttp的操作。所以这就是需要两个Call的原因,Retrofit.Call封装了OkHttp.Call的一些操作。

讲完这些对Retrofit应该有个大概的了解了。那么再讲讲Converter,其实就是对数据的转换。
最常见的就是json的转换,比如说我拿到一个json的字符串:

[
    {
        "login":"ssss",
        "contributions":1
    },
    {
        "login":"ssss",
        "contributions":2
    }
]

我希望我只用定义

class Contributor {
        String login;
        int contributions;
    }

转换库可以在拿到服务器数据后自动帮我填充到类的变量上去。
这样类似的库有很多,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

具体功能可以自己去查。那么我们可以自定义这样的converter么?当然可以!
看下Retrofit提供的接口:

public interface Converter<F, T> {
  T convert(F value) throws IOException;

  /** Creates {@link Converter} instances based on a type and target usage. */
  abstract class Factory {
    /**
     * Returns a {@link Converter} for converting an HTTP response body to {@code type}, or null if
     * {@code type} cannot be handled by this factory. This is used to create converters for
     * response types such as {@code SimpleResponse} from a {@code Call<SimpleResponse>}
     * declaration.
     */
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    /**
     * 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 Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

    /**
     * Returns a {@link Converter} for converting {@code type} to a {@link String}, or null if
     * {@code type} cannot be handled by this factory. This is used to create converters for types
     * specified by {@link Field @Field}, {@link FieldMap @FieldMap} values,
     * {@link Header @Header}, {@link HeaderMap @HeaderMap}, {@link Path @Path},
     * {@link Query @Query}, and {@link QueryMap @QueryMap} values.
     */
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }
  }
}

responseBodyConverter就是对返回数据做转换,requestBodyConverter是对请求数据做转换。(看了这一对函数,其实觉得有点像OkHttp拦截器,也是分请求前和请求后的。)
举个具体的例子:如果服务器返回的数据都是使用Base64解密,那么对于客户端拿到数据之前应该先解密再使用,那么如何用converter来实现呢?
上代码:

public class Base64GsonConverterFactory extends Converter.Factory {

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

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

    private final Gson gson;

    private Base64GsonConverterFactory(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 Base64GsonBodyConverter<>(adapter);
    }

    private static class Base64GsonBodyConverter<T> implements Converter<ResponseBody, T> {
        private final TypeAdapter<T> adapter;

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

        @Override
        public T convert(ResponseBody value) throws IOException {
            String temp = value.string();
            temp = new String(Base64.decode(temp, Base64.DEFAULT));
            return adapter.fromJson(temp);
        }
    }
}

该Factory继承自Converter.Factory,只重写了responseBodyConverter,说明只对返回的数据进行转换,请求前不转换。在处理responseBody的时候,使用Base64GsonBodyConverter来转换,进行base64解码。当然解码后还支持gson的转换。
从此转换数据是不是变的很方便?如果要使用这些converter的话,只用add进去就好了。

    private static Retrofit.Builder builder = new Retrofit.Builder()
            .baseUrl(API_BASE_URL)
            .addConverterFactory(Base64GsonConverterFactory.create())
            .addConverterFactory(ScalarsConverterFactory.create())
            .addConverterFactory(GsonConverterFactory.create());

那么问题来了!这么多的converter,我返回数据只有一个,不可能说每个converter都处理啊,这和RxJava可不一样,不是一个事件流处理,一个response只能被一个converter处理,那么那个来处理呢?
在Retrofit中维护一个converters的列表,记录着所有支持的converter。还记得刚刚的这个方法么?

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

在该方法中的第一个参数会返回该请求需要返回的类型,也就是刚刚我们说的Retrofit.Call<T>,也就是T类型,每个converter可以只处理自己想要处理的T的类型,举个例子,看下ScalarsConverterFactory这个converter处理的类型:

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

  private ScalarsConverterFactory() {
  }

  @Override public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    if (type == String.class
        || type == boolean.class
        || type == Boolean.class
        || type == byte.class
        || type == Byte.class
        || type == char.class
        || type == Character.class
        || type == double.class
        || type == Double.class
        || type == float.class
        || type == Float.class
        || type == int.class
        || type == Integer.class
        || type == long.class
        || type == Long.class
        || type == short.class
        || type == Short.class) {
      return ScalarRequestBodyConverter.INSTANCE;
    }
    return null;
  }

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
      Retrofit retrofit) {
    if (type == String.class) {
      return StringResponseBodyConverter.INSTANCE;
    }
    if (type == Boolean.class || type == boolean.class) {
      return BooleanResponseBodyConverter.INSTANCE;
    }
    if (type == Byte.class || type == byte.class) {
      return ByteResponseBodyConverter.INSTANCE;
    }
    if (type == Character.class || type == char.class) {
      return CharacterResponseBodyConverter.INSTANCE;
    }
    if (type == Double.class || type == double.class) {
      return DoubleResponseBodyConverter.INSTANCE;
    }
    if (type == Float.class || type == float.class) {
      return FloatResponseBodyConverter.INSTANCE;
    }
    if (type == Integer.class || type == int.class) {
      return IntegerResponseBodyConverter.INSTANCE;
    }
    if (type == Long.class || type == long.class) {
      return LongResponseBodyConverter.INSTANCE;
    }
    if (type == Short.class || type == short.class) {
      return ShortResponseBodyConverter.INSTANCE;
    }
    return null;
  }
}

这个ScalarsConverterFactory只处理String、boolean这种基本类型的数据,而其他类型的response都不会处理,直接返回null。
当一个response返回后,retrofit会遍历converters,并将response的type传给converter,询问该converter是否要处理,如果返回的不为空,则代表可以处理,不再遍历列表。如果遍历完都没有一个converter愿意处理的话,也会提示。所以添加converter的顺序很重要,像GsonConverterFactory这种是所有类型都会处理的,要放在后面添加,否则如果直接返回“string”的话,不为json,解析会出错。
所以小集合的converter放前面,大集合的converter放后面。

Retrofit官网:

上面讲了基本的一些用法和概念,下面讲些其他的小点:

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTask(@Query("id") List<Long> taskIds);
}
  • Send Objects in Request Body:使用post请求发送请求时,需将参数放在body中传输。刚刚我们前面讲的都是get请求,按照HTTP协议规范,GET请求的参数应该放在url中,而不是body中,GET请求的body为空。所以我们前面@query是将参数放在url中,那现在如果post要传递参数则使用@Body注解来,例如:
public interface TaskService {  
    @POST("/tasks")
    Call<Task> createTask(@Body Task task);
}

public class Task {  
    private long id;
    private String text;

    public Task(long id, String text) {
        this.id = id;
        this.text = text;
    }
}

Task task = new Task(1, "my task title");  
Call<Task> call = taskService.createTask(task);  
call.enqueue(new Callback<Task>() {});  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值