retrofit2之源码解析1

1. 你真的会用Retrofit2吗? Retrofit2完全教程?

2.Retrofit2 源码解析

一.Retrofit2 源码解析

 总结:

   Retrofit非常巧妙的用注解-来描述一个HTTP请求,将一个HTTP请求抽象成一个Java接口,然后用了Java动态代理的方式,动态的将这个接口的注解“翻译”成一个HTTP请求,最后再执行这个HTTP请求。 

Retrofit的功能非常依赖Java反射,代码中其实还有很多细节,比如异常的捕获、抛出和处理,大量的Factory设计模式(为什么要这么多使用Factory模式?)

Retrofit中接口设计的恰到好处,在你创建Retrofit对象时,让你有更多更灵活的方式去处理你的需求,比如使用不同的Converter、使用不同的CallAdapter,这也就提供了你使用RxJava来调用Retrofit的可能

我也慢慢看了Picasso和Retrofit的代码了,收获还是很多的,也更加深入理解面向接口的编程方法,这个写代码就是好的代码,就是依赖接口而不是实现最好的例子

 什么是好的代码?像Picasso和Retrofit这样的就是好的代码,扩展性强、低耦合、插件化  Square开源的OkHttp或者Retrofit等

我觉得Retrofit 无疑是这几个当中最好用的一个,设计这个库的思路很特别而且巧妙。Retrofit代码很少,花点时间读它的源码肯定会收获很多

本文的源码分析基于Retrofit 2,和Retrofit 1.0的Api有较大不同,

本文主要分为几部分:

0、Retrofit 是什么,

1、Retrofit怎么用,

2、Retrofit的原理是什么,

3、我的心得与看法

1 Retrofit是什么

    简单说它是一个基于OkHttp的RESTFUL Api请求工具,从功能上来说和Google的Volley功能上很相似,但是使用上很不相似。

Volley使用上更加原始而且符合使用者的直觉,当App要发送一个Http请求时,你需要先创建一个Request对象,指定这个Request用的是GET、POST或其他方法,一个api 地址,一个处理response的回调,

如果是一个POST请求,那么你还需要给这个Request对象设置一个body,

有时候你还需要自定义添加Header什么的,然后将这个Request对象添加到RequestQueue中,接下去检查Cache以及发送Http请求的事情,Volley会帮你处理。

如果一个App中api不同的api请求很多,这样代码就会很难看。而Retrofit可以简单到调用一个Java方法方式去请求一个api,这样App中的代码就会很简洁方便阅读

1.1 Retrofit怎么用 

首先,你需要创建一个Retrofit对象,并且指定api的域名:
public static final String API_URL = "https://zhuanlan.zhihu.com";
Create a very simple REST adapter which points the Zhuanlan API.
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(API_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();
其次,根据api新建一个Java接口,用Java注解来描述这个api
public interface ZhuanLanApi {
  @GET("/api/columns/{user} ")
  Call<ZhuanLanAuthor> getAuthor(@Path("user") String user)}
再用这个retrofit对象创建一个ZhuanLanApi对象:
ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);
Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");
这样就表示你要请求的api是https://zhuanlan.zhihu.com/api/columns/qinchao
最后你就可以用这个call对象获得数据了,enqueue方法是异步发送http请求的,
如果你想用同步的方式发送可以使用execute()方法,call对象还提供cancel()、isCancel()等方法获取这个Http请求的状态
//请求数据,并且处理response
call.enqueue(new Callback<ZhuanLanAuthor>() {
    @Override
    public void onResponse(Response<ZhuanLanAuthor> author) {
        System.out.println("name: " + author.getName());
    }
    @Override
    public void onFailure(Throwable t) { }});
看到没,Retrofit只要创建一个接口来描述Http请求,然后可以让我们可以像调用Java方法一样请求一个Api!

2 Retrofit的原理

    从上面Retrofit的使用来看,Retrofit就是充当了一个适配器(Adapter)角色:将一个Java接口翻译成一个Http请求,然后用OkHttp去发送这个请求**

Volley描述一个HTTP请求是需要创建一个Request对象,而执行这个请求呢,就是把这个请求对象放到一个队列中,在网络线程中用HttpUrlConnection去请求

问题来了:Retrofit是怎么做的呢? 就是Java的动态代理

动态代理

我刚开始看Retrofit的代码,我对下面这句代码感到很困惑:
ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);

我给Retrofit对象传了一个ZhuanLanApi接口的Class对象,怎么又返回一个ZhuanLanApi对象呢?

进入create方法一看,但是我觉得这几行代码就是Retrofit的精妙的地方

/** Create an implementation of the API defined by the {@code service} interface. */
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);
      } });}

create方法就是返回了一个Proxy.newProxyInstance动态代理对象。

动态代理是个什么东西?

   Java动态代理就是给了程序员一种可能: 当你要调用某个Class的方法前或后,插入你想要执行的代码

比如你要执行某个操作前,你必须要判断这个用户是否登录,或者 你在付款前,你需要判断这个人的账户中存在这么多钱。

为什么要使用动态代理?

获取数据的代码就是这句:
Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");

上面api对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi接口的implements产生的对象,当api对象调用getAuthor方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象,它的invoke方法会传入3个参数:

  • Object proxy: 代理对象,不关心这个
  • Method method:调用的方法,就是getAuthor方法
  • Object... args:方法的参数,就是"qinchao"

而Retrofit关心的就是method和它的参数args,接下去Retrofit就会用Java反射获取到getAuthor方法的注解信息,配合args参数,创建一个ServiceMethod对象

ServiceMethod就像是一个中央处理器,传入Retrofit对象和Method对象,调用各个接口和解析器,最终生成一个Request,包含api 的域名、path、http请求方法、请求头、是否有body、是否是multipart等等。

最后返回一个Call对象,Retrofit2中Call接口的默认实现是OkHttpCall,它默认使用OkHttp3作为底层http请求client

使用Java动态代理的目的就要拦截被调用的Java方法,然后解析这个Java方法的注解,最后生成Request由OkHttp发送

3. Retrofit的源码分析

  想要弄清楚Retrofit的细节,Retrofit源码组成:

  1. 一个retrofit2.http包,里面全部是定义HTTP请求的Java注解,比如GETPOSTPUTDELETEHeadersPathQuery等等
  2. 余下的retrofit2包中几个类和接口就是全部retrofit的代码了,代码真的很少,很简单,因为retrofit把网络请求这部分功能全部交给了OkHttp了

Retrofit接口

  Retrofit的设计,非常插件化而且轻量级,真的是非常高内聚而且低耦合,这个和它的接口设计有关。Retrofit中定义了4个接口:

Callback<T>

这个接口就是retrofit请求数据返回的接口,只有两个方法

  • void onResponse(Response<T> response);
  • void onFailure(Throwable t);

Converter<F, T>

这个接口主要的作用就是将HTTP返回的数据解析成Java对象,主要有Xml、Gson、protobuf等等,你可以在创建Retrofit对象时添加你需要使用的Converter实现(看上面创建Retrofit对象的代码)

Call<T>

这个接口主要的作用就是发送一个HTTP请求,Retrofit默认的实现是OkHttpCall<T>

你可以根据实际情况实现你自己的Call类,这个设计和Volley的HttpStack接口设计的思想非常相似,子类可以实现基于HttpClientHttpUrlConnetction的HTTP请求工具,这种设计非常的插件化,而且灵活

CallAdapter<T>

上面说到过,CallAdapter中属性只有responseType一个,还有一个<R> T adapt(Call<R> call)方法,这个接口的实现类也只有一个,DefaultCallAdapter。这个方法的主要作用就是将Call对象 转换成另一个对象,可能是为了支持RxJava才设计这个类的吧

Retrofit的运行过程:

上面讲到ZhuanLanApi api = retrofit.create(ZhuanLanApi.class);代码返回了一个动态代理对象,

而执行Call<ZhuanLanAuthor> call = api.getAuthor("qinchao");代码时返回了一个OkHttpCall对象,拿到这个Call对象才能执行HTTP请求

上面api对象其实是一个动态代理对象,并不是一个真正的ZhuanLanApi接口的implements产生的对象,

api对象调用getAuthor方法时会被动态代理拦截,然后调用Proxy.newProxyInstance方法中的InvocationHandler对象, 创建一个ServiceMethod对象:

ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);

创建ServiceMethod

刚才说到,ServiceMethod就像是一个中央处理器,具体来看一下创建这个ServiceMethod的过程是怎么样的

第一步,获取到上面说到的3个接口对象:

callAdapter = createCallAdapter();
responseType = callAdapter.responseType();
responseConverter = createResponseConverter();

第二步,解析Method注解,主要就是获取Http请求的方法,比如是GET还是POST还是其他形式,如果没有,程序就会报错,还会做一系列的检查,比如如果在方法上注解了@Multipart,但是Http请求方法是GET,同样也会报错。因此,在注解Java方法是需要严谨

for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");}

第三步,比如上面api中带有一个参数{user},这是一个占位符,而真实的参数值在Java方法中传入,那么Retrofit会使用一个ParameterHandler来进行替换:

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];

最后,ServiceMethod会做其他的检查,比如用了@FormUrlEncoded注解,那么方法参数中必须至少有一个@Field@FieldMap

执行Http请求:

之前讲到,OkHttpCall是实现了Call接口的,并且是真正调用OkHttp3发送Http请求的类。OkHttp3发送一个Http请求需要一个Request对象,而这个Request对象就是从ServiceMethodtoRequest返回的

总的来说,OkHttpCall就是调用ServiceMethod获得一个可以执行的Request对象,然后等到Http请求返回后,再将response body传入ServiceMethod中,ServiceMethod就可以调用Converter接口将,response body转成一个Java对象

结合上面说的就可以看出,ServiceMethod中几乎保存了一个api请求所有需要的数据,OkHttpCall需要从ServiceMethod中获得一个Request对象,然后得到response后,还需要传入ServiceMethodConverter转换成Java对象

你可能会觉得我只要发送一个HTTP请求,你要做这么多事情不会很“慢”吗?不会很浪费性能吗?

我觉得,首先现在手机处理器主频非常高了,解析这个接口可能就花1ms可能更少的时间(我没有测试过),面对一个HTTP本来就需要几百ms,甚至几千ms来说不值得一提;

而且Retrofit会对解析过的请求进行缓存,就在Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();这个对象中

如何在Retrofit中使用RxJava

由于Retrofit设计的扩展性非常强,你只需要添加一个CallAdapter就可以

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();

上面代码创建了一个Retrofit对象,支持Proto和Gson两种数据格式,并且还支持RxJava

二. 你真的会用Retrofit2吗?Retrofit2完全教程?

本文注目录:

  • Retrofit入门
  • Retrofit注解详解
  • Gson与Converter
  • RxJava与CallAdapter
  • 自定义Converter
  • 自定义CallAdapter
  • 其它说明

本文中指Retrofit2.0。
本文涉及到的代码以及测试使用的接口可在Github上找到。
测试接口服务器在 server 项目下,直接运行 RESTServer.main() 即可启动测试服务器,所面代码示例均使用该接口(接口地址 http://localhost:4567/ ).
当然你也可以自己借助 json-server 或 最新开源的Parse 搭建一个REST API, 

前面写了你应该知道的HTTP基础知识 介绍了HTTP的相关知识, 

1、Retrofit入门

Retrofit 其实相当简单,简单到源码只有37个文件,其中22个文件是注解还都和HTTP有关,真正暴露给用户的类并不多,

所以我看了一遍 官方教程 大多数情景就可以无障碍使用

1.1、创建Retrofit实例

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:4567/")
.build();

注1: Retrofit2 的baseUlr 必须以 /(斜线) 结束,不然会抛出一个IllegalArgumentException,所以如果你看到别的教程没有以 / 结束,那么多半是直接从Retrofit 1.X 照搬过来的。
注2: 上面的 注1 应该描述为 baseUrl 中的路径(path)必须以 / 结束, 因为有些特殊情况可以不以/结尾(感谢@liujc 提出,81楼),

比如 其实这个 URL https://www.baidu.com?key=value用来作为baseUrl其实是可行的,因为这个URL隐含的路径就是 /(斜线,代表根目录) ,而后面的?key=value在拼装请求时会被丢掉所以写上也没用。之所以 Retrofit 2 在文档上要求必须以 /(斜线) 结尾的要求想必是要消除歧义以及简化规则。——2017.10.27

1.2、接口定义

以获取指定id的Blog为例:
public interface BlogService {
    @GET("blog/{id}")
    Call<ResponseBody> getBlog(@Path("id") int id);
}
注意,这里是interface不是class,所以我们是无法直接调用该方法,我们需要用Retrofit创建一个BlogService的代理对象。
BlogService service = retrofit.create(BlogService.class);拿到代理对象之后,就可以调用该方法啦。

1.3、接口调用

Call<ResponseBody> call = service.getBlog(2);
// 用法和OkHttp的call如出一辙,
// 不同的是如果是Android系统回调方法执行在主线程
call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        try {
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }}
    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        t.printStackTrace();
    }});

2、Retrofit注解详解

上面提到Retrofit 共22个注解,这节就专门介绍这22个注解,为帮助大家更好理解将这22个注解分为三类,并用表格的形式展现出来,表格上说得并不完整,具体的见源码上的例子注释。

第一类:HTTP请求方法 

HTTP请求方法注解

以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解,有3个属性:methodpath,hasBody,
下面是用HTTP注解实现上面 Example01.java 的例子。
public interface BlogService {
    /**
     * method 表示请求的方法,区分大小写
     * path表示路径
     * hasBody表示是否有请求体
     */
    @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getBlog(@Path("id") int id);}

注:method 的值 retrofit 不会做处理,所以要自行保证其准确性,之前使用小写也可以是因为示例源码中的服务器不区分大小写,所以希望大家注意,感谢 @言過祺實 发现该问题。
示例源码见 Example02.java

第二类:标记类 

标记类注解

示例源码见 Example03.java

第三类:参数类

参数类注解

注1:{占位符}和PATH尽量只用在URL的path部分,url中的参数使用QueryQueryMap 代替,保证接口定义的简洁
注2:QueryFieldPart这三者都支持数组和实现了Iterable接口的类型,如ListSet等,方便向后台传递数组。

Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
//结果:ids[]=0&ids[]=1&ids[]=2

Path 示例源码见 Example01.java
Field、FieldMap、Part和PartMap 示例源码见 Example03.java
Header和Headers 示例源码见 Example04.java
Query、QueryMap、Url 示例源码见 Example05.java

3、Gson与Converter

在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,
这也是为什么我在前面的例子接口的返回值都是 Call<ResponseBody>
但如果响应体只是支持转换为ResponseBody的话何必要引入泛型呢,
返回值直接用一个Call就行了嘛,既然支持泛型,那说明泛型参数可以是其它类型的,
Converter就是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型,
有了Converter之后我们就可以写把我们的第一个例子的接口写成这个样子了:

public interface BlogService {
  @GET("blog/{id}")
  Call<Result<Blog>> getBlog(@Path("id") int id);
}
当然只改变泛型的类型是不行的,我们在创建Retrofit时需要明确告知用于将ResponseBody转换我们泛型中的类型时需要使用的Converter
引入Gson支持: compile 'com.squareup.retrofit2:converter-gson:2.0.2'
通过GsonConverterFactory为Retrofit添加Gson支持:
Gson gson = new GsonBuilder()
      .setDateFormat("yyyy-MM-dd hh:mm:ss").create();
Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      //可以接收自定义的Gson,当然也可以不传
      .addConverterFactory(GsonConverterFactory.create(gson)) .build();
示例源码见 Example06.java
这样Retrofit就会使用Gson将ResponseBody转换我们想要的类型。
演示如使创建一个Blog了!
@POST("blog")
Call<Result<Blog>> createBlog(@Body Blog blog);
被@Body注解的的Blog将会被Gson转换成RequestBody发送到服务器。
BlogService service = retrofit.create(BlogService.class);
Blog blog = new Blog();
blog.content = "新建的Blog";
blog.title = "测试";
blog.author = "怪盗kidou";
Call<Result<Blog>> call = service.createBlog(blog);
结果:Result{code=200, msg='OK', data=Blog{id=20, date='2016-04-21 05:29:58', author='怪盗kidou', title='测试', content='新建的Blog'}, count=0, page=0}
4、RxJava与CallAdapter

      说到Retrofit就不得说到另一个火到不行的库RxJava,网上已经不少文章讲如何与Retrofit结合,但这里还是会有一个RxJava的例子,

不过这里主要目的是介绍使用CallAdapter所带来的效果。

第3节介绍的Converter是对于Call<T>T的转换,而CallAdapter则可以对Call转换,这样的话Call<T>中的Call也是可以被替换的,而返回值的类型就决定你后续的处理程序逻辑, 同样Retrofit提供了多个CallAdapter,这里以RxJava的为例,用Observable代替Call

引入RxJava支持:
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
// 针对rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持:
Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      .addConverterFactory(GsonConverterFactory.create())
      .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
      // 针对rxjava2.x
      .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
      .build();
接口设计:
public interface BlogService {
  @POST("/blog")
  Observable<Result<List<Blog>>> getBlogs();
}
使用:
BlogService service = retrofit.create(BlogService.class);
service.getBlogs(1)
  .subscribeOn(Schedulers.io())
  .subscribe(new Subscriber<Result<List<Blog>>>() {
      @Override
      public void onCompleted() {
        System.out.println("onCompleted");}
      @Override
      public void onError(Throwable e) {
        System.err.println("onError"); }
      @Override
      public void onNext(Result<List<Blog>> blogsResult) {
        System.out.println(blogsResult);}});
结果:
Result{code=200, msg='OK', data=[Blog{id=1, date='2016-04-15 03:17:50', author='怪盗kidou', title='Retrofit2 测试1',
content='这里是 Retrofit2 Demo 测试服务器1'} ,.....], count=20, page=1}

「20160608补充」:像上面的这种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:
1、用Observable<Response<T>> 代替 Observable<T> ,这里的Responseretrofit2.Response
2、用Observable<Result<T>> 代替 Observable<T>,这里的Result是指retrofit2.adapter.rxjava.Result, 这个Result中包含了Response的实例

5、自定义Converter

本节的内容是教大家实现在一简易的Converter,这里以返回格式为Call<String>为例。

在此之前先了解一下Converter接口及其作用:

public interface Converter<F, T> {
  // 实现从 F(rom) 到 T(o)的转换
  T convert(F value) throws IOException;
  // 用于向Retrofit提供相应Converter的工厂
  abstract class Factory {
    // 这里创建从ResponseBody其它类型的Converter,如果不能处理返回null
    // 主要用于对响应体的处理
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
      return null;
    }
    // 在这里创建 从自定类型到ResponseBody 的Converter,不能处理就返回null,
    // 主要用于对Part、PartMap、Body注解的处理
    public Converter<?, RequestBody> requestBodyConverter(Type type,
    Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }
    // 这里用于对Field、FieldMap、Header、Path、Query、QueryMap注解的处理
    // Retrfofit对于上面的几个注解默认使用的是调用toString方法
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
      return null;}}}
我们要想从Call<ResponseBody> 转换为 Call<String> 那么对应的F和T则分别对应ResponseBody和String,我们定义一个StringConverter并实现Converter接口。
public static class StringConverter implements Converter<ResponseBody, String> {
  public static final StringConverter INSTANCE = new StringConverter();
  @Override
  public String convert(ResponseBody value) throws IOException {
    return value.string();
  }}
我们需要一个Fractory来向Retrofit注册StringConverter
public static class StringConverterFactory extends Converter.Factory {
  public static final StringConverterFactory INSTANCE = new StringConverterFactory();
  public static StringConverterFactory create() {
    return INSTANCE;}
  // 我们只关实现从ResponseBody 到 String 的转换,所以其它方法可不覆盖
  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
    if (type == String.class) {
      return StringConverter.INSTANCE;
    }
    return null; //其它类型我们不处理,返回null就行
  }}
使用Retrofit.Builder.addConverterFactory向Retrofit注册我们StringConverterFactory:
Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      // 我们自定义的一定要放在Gson这类的Converter前面 
      .addConverterFactory(StringConverterFactory.create())
      .addConverterFactory(GsonConverterFactory.create())
      .build();

注:addConverterFactory是有先后顺序的,如果有多个ConverterFactory都支持同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory是不判断是否支持的,所以这里交换了顺序还会有一个异常抛出,原因是类型不匹配。

只要返回值类型的泛型参数就会由我们的StringConverter处理,不管是Call<String>还是Observable<String>

有没有很简单?如果你有其它的需求处理的就自己实现吧。

示例源码见 Example09.java

6、自定义CallAdapter

本节将介绍如何自定一个CallAdapter,并验证是否所有的String都会使用我们第5节中自定义的Converter。

先看一下CallAdapter接口定义及各方法作用:

public interface CallAdapter<T> {
  // 直正数据的类型 如Call<T> 中的 T
  // 这个 T 会作为Converter.Factory.responseBodyConverter 的第一个参数
  // 可以参照上面的自定义Converter
  Type responseType();
  <R> T adapt(Call<R> call);
  // 用于向Retrofit提供CallAdapter的工厂类
  abstract class Factory {
    // 在这个方法中判断是否是我们支持的类型,returnType 即Call<Requestbody>和`Observable<Requestbody>`
    // RxJavaCallAdapterFactory 就是判断returnType是不是Observable<?> 类型
    // 不支持时返回null
    public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,Retrofit retrofit);
    // 用于获取泛型的参数 如 Call<Requestbody> 中 Requestbody
    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }
    // 用于获取泛型的原始类型 如 Call<Requestbody> 中的 Call
    // 上面的get方法需要使用该方法。
    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }}}
了解了CallAdapter的结构和其作用之后,我们就可以开始自定义我们的CallAdapter了,本节以CustomCall<String>为例。
在此我们需要定义一个CustomCall,不过这里的CustomCall作为演示只是对Call的一个包装,并没有实际的用途。
public static class CustomCall<R> {
  public final Call<R> call;
  public CustomCall(Call<R> call) {
    this.call = call;
  }
  public R get() throws IOException {
    return call.execute().body();
  }}
有了CustomCall,我们还需要一个CustomCallAdapter来实现 Call<T> 到 CustomCall<T>的转换,这里需要注意的是最后的泛型,是我们要返回的类型。
public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> {
  private final Type responseType;
  // 下面的 responseType 方法需要数据的类型
  CustomCallAdapter(Type responseType) {
    this.responseType = responseType;}
  @Override
  public Type responseType() {
    return responseType;
  }
  @Override
  public <R> CustomCall<R> adapt(Call<R> call) {
    // 由 CustomCall 决定如何使用
    return new CustomCall<>(call);}}
提供一个CustomCallAdapterFactory用于向Retrofit提供CustomCallAdapter:
public static class CustomCallAdapterFactory extends CallAdapter.Factory {
  public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();
  @Override
  public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    // 获取原始类型
    Class<?> rawType = getRawType(returnType);
    // 返回值必须是CustomCall并且带有泛型
    if (rawType == CustomCall.class && returnType instanceof ParameterizedType) {
      Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
      return new CustomCallAdapter(callReturnType);
    }
    return null;}}
使用addCallAdapterFactory向Retrofit注册CustomCallAdapterFactory
Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      .addConverterFactory(Example09.StringConverterFactory.create())
      .addConverterFactory(GsonConverterFactory.create())
      .addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)
      .build();

注: addCallAdapterFactoryaddConverterFactory同理,也有先后顺序。

示例源码见 Example10.java

7、其它说明

7.1 Retrofit.Builder

前面用到了 Retrofit.Builder 中的baseUrladdCallAdapterFactoryaddConverterFactorybuild方法,

还有callbackExecutorcallFactoryclientvalidateEagerly这四个方法没有用到,简单介绍一下。

方法用途
callbackExecutor(Executor) 指定Call.enqueue时使用的Executor,所以该设置只对返回值为Call的方法有效
callFactory(Factory) 设置一个自定义的okhttp3.Call.Factory,那什么是Factory呢?OkHttpClient就实现了okhttp3.Call.Factory接口,下面的client(OkHttpClient)最终也是调用了该方法,也就是说两者不能共用
client(OkHttpClient)

设置自定义的OkHttpClient,以前的Retrofit版本中不同的Retrofit对象共用同OkHttpClient,

在2.0各对象各自持有不同的OkHttpClient实例,所以当你需要共用OkHttpClient或 需要自定义时则可以使用该方法,如:处理Cookie、使用stetho 调式等

validateEagerly(boolean) 是否在调用create(Class)时检测接口定义是否正确,而不是在调用方法才检测,适合在开发、测试时使用

7.2 Retrofit的Url组合规则

BaseUrl和URL有关的注解中提供的值最后结果
http://localhost:4567/path/to/other/ /post http://localhost:4567/post
http://localhost:4567/path/to/other/ post http://localhost:4567/path/to/other/post
http://localhost:4567/path/to/other/ https://github.com/ikidou https://github.com/ikidou

从上面不能难看出以下规则:

  • 如果你在注解中提供的url是完整的url,则url将作为请求的url。
  • 如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值
  • 如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值

7.3 Retrofit提供的Converter

ConverterGradle依赖
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2

7.4 Retrofit提供的CallAdapter:

CallAdapterGradle依赖
guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

7.5 关于源码

看到这儿可能有小伙伴要问为什么源码没有把类拆分到单独的文件,命名也不能体现其用途,这里主要是因为方便大家看源码,而不是将注意力放在反复跳转上,

另一方面也是因为同一个例子中不可避免的使用其它小节要介绍的内容,所以就直接用了ExampleXX的形式,不过在项目中千万不要使用这种方式,一定要好好命名,做到见名知意。

结语

其它本博客的内容早就已经完成好了,但由于当时HTTP、反射、注解的博客一篇也没有写,所以一直没有发,期间也有不少的博主写了Retrofit2的博文,不过呢没有自定义相关的内容,也没有对各个注解进行详解,所以我还是决定发出来帮助一下那此对Retrofit2无从下手同鞋。

 

posted on 2020-03-21 19:06  左手指月  阅读(537)  评论(0编辑  收藏  举报