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

来源:互联网 发布:程序员远程工作招聘 编辑:程序博客网 时间:2024/06/06 03:40

本文是围绕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。还记得刚刚的这个方法么?

@Overridepublic 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官网:

  • Retrofit 2 — Adding & Customizing the Gson Converter
  • Retrofit — Getting Started and Create an Android Client
  • https://github.com/square/retrofit/wiki/Converters

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

  • Multiple Query Parameters of Same Name:如果我们要发出这样的url(https://api.example.com/tasks?id=123&id=124&id=125),参数中有多个名字相同的参数,该怎么做?
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 0
原创粉丝点击