当前位置: 代码迷 >> 综合 >> 深入浅出 Retrofit
  详细解决方案

深入浅出 Retrofit

热度:28   发布时间:2023-12-12 22:49:26.0
Android 开发中,从原生的 HttpUrlConnection 到经典的 Apache 的 HttpClient,再到对前面这些网络基础框架的封装,比如 Volley、Async Http Client,Http 相关开源框架的选择还是很多的,其中由著名的 Square 公司开源的 Retrofit 更是以其简易的接口配置、强大的扩展支持、优雅的代码结构受到大家的追捧。也正是由于 Square 家的框架一如既往的简洁优雅,所以我一直在想,Square 公司是不是只招处女座的程序员?

1、初识 Retrofit
单从 Retrofit 这个单词,你似乎看不出它究竟是干嘛的,当然,我也看不出来 :)逃。。
Retrofitting refers to the addition of new technology or features to older systems.
—From Wikipedia

于是我们就明白了,冠以 Retrofit 这个名字的这个家伙,应该是某某某的 『Plus』 版本了。

1.1 Retrofit 概览
Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。注意这里并没有说它是网络请求框架,主要原因在于网络请求的工作并不是 Retrofit 来完成的。Retrofit 2.0 开始内置 OkHttp,前者专注于接口的封装,后者专注于网络请求的高效,二者分工协作,宛如古人的『你耕地来我织布』,小日子别提多幸福了。
我们的应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作,在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,后者根据用户的需求对结果进行解析的过程。
讲到这里,你就会发现所谓 Retrofit,其实就是 Retrofitting OkHttp 了。

1.2 Hello Retrofit
多说无益,不要来段代码陶醉一下。使用 Retrofit 非常简单,首先你需要在你的 build.gradle 中添加依赖:
[Groovy]  纯文本查看  复制代码
?
1
compile 'com.squareup.retrofit2:retrofit:2.0.2'
你一定是想要访问 GitHub 的 api 对吧,那么我们就定义一个接口:
[Java]  纯文本查看  复制代码
?
1
2
3
4
public interface GitHubService {
   @GET ( "users/{user}/repos" )
   Call<List<Repo>> listRepos( @Path ( "user" ) String user);
}
接口当中的 listRepos 方法,就是我们想要访问的 api 了:
https://api.github.com/users/{user}/repos
其中,在发起请求时, {user} 会被替换为方法的第一个参数 user。
好,现在接口有了,我们要构造 Retrofit 了:
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder()
     .baseUrl( "https://api.github.com/" )
     .build();
GitHubService service = retrofit.create(GitHubService. class );
这里的 service 就好比我们的快递哥,还是往返的那种哈~
[Java]  纯文本查看  复制代码
?
1
Call<List<Repo>> repos = service.listRepos( "octocat" );
发请求的代码就像前面这一句,返回的 repos 其实并不是真正的数据结果,它更像一条指令,你可以在合适的时机去执行它:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
// 同步调用
List<Repo> data = repos.execute();
// 异步调用
repos.enqueue( new Callback<List<Repo>>() {
             @Override
             public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
                 List<Repo> data = response.body();
             }
             @Override
             public void onFailure(Call<List<Repo>> call, Throwable t) {
                 t.printStackTrace();
             }
         });
啥感觉?有没有突然觉得请求接口就好像访问自家的方法一样简单?呐,前面我们看到的,就是 Retrofit 官方的 demo 了。你以为这就够了?噗~怎么可能。。

1.3 Url 配置
Retrofit 支持的协议包括 GET/POST/PUT/DELETE/HEAD/PATCH,当然你也可以直接用 HTTP 来自定义请求。这些协议均以注解的形式进行配置,比如我们已经见过 GET 的用法:
[Java]  纯文本查看  复制代码
?
1
2
@GET ( "users/{user}/repos" )
Call<List<Repo>> listRepos( @Path ( "user" ) String user);
这些注解都有一个参数 value,用来配置其路径,比如示例中的 users/{user}/repos,我们还注意到在构造 Retrofit 之时我们还传入了一个 baseUrl("https://api.github.com/"),请求的完整 Url 就是通过 baseUrl 与注解的 value(下面称 “path“ ) 整合起来的,具体整合的规则如下:
  • path 是绝对路径的形式:
    path = "/apath",baseUrl = "http://host:port/a/b"
      Url = "http://host:port/apath"
  • path 是相对路径,baseUrl 是目录形式:
      path = "apath",baseUrl = "http://host:port/a/b/"
      Url = "http://host:port/a/b/apath"
  • path 是相对路径,baseUrl 是文件形式:
      path = "apath",baseUrl = "http://host:port/a/b"
      Url = "http://host:port/a/apath"
  • path 是完整的 Url:
      path = "http://host:port/aa/apath",baseUrl = "http://host:port/a/b"
      Url = "http://host:port/aa/apath"


建议采用第二种方式来配置,并尽量使用同一种路径形式。如果你在代码里面混合采用了多种配置形式,恰好赶上你哪天头晕眼花,信不信分分钟写一堆 bug 啊哈哈。


1.4 参数类型
发请求时,需要传入参数,Retrofit 通过注解的形式令 Http 请求的参数变得更加直接,而且类型安全。

1.4.1 Query & QueryMap
[Java]  纯文本查看  复制代码
?
1
2
@GET ( "/list" )
Call<ResponseBody> list( @Query ( "page" ) int page);

Query 其实就是 Url 中 ‘?’ 后面的 key-value,比如:
http://www.println.net/?cate=android
这里的 cate=android 就是一个 Query,而我们在配置它的时候只需要在接口方法中增加一个参数,即可:
[Java]  纯文本查看  复制代码
?
1
2
3
4
interface PrintlnServer{
     @GET ( "/" )
     Call<String> cate( @Query ( "cate" ) String cate);
}

这时候你肯定想,如果我有很多个 Query,这么一个个写岂不是很累?而且根据不同的情况,有些字段可能不传,这与方法的参数要求显然也不相符。于是,打群架版本的 QueryMap 横空出世了,使用方法很简单,我就不多说了。

1.4.2 Field & FieldMap
其实我们用 POST 的场景相对较多,绝大多数的服务端接口都需要做加密、鉴权和校验,GET 显然不能很好的满足这个需求。使用 POST 提交表单的场景就更是刚需了,怎么提呢?
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
@FormUrlEncoded
@POST ( "/" )
Call<ResponseBody> example(
     @Field ( "name" ) String name,
     @Field ( "occupation" ) String occupation);

其实也很简单,我们只需要定义上面的接口就可以了,我们用 Field 声明了表单的项,这样提交表单就跟普通的函数调用一样简单直接了。
等等,你说你的表单项不确定个数?还是说有很多项你懒得写?Field 同样有个打群架的版本——FieldMap,赶紧试试吧~~

1.4.3 Part & PartMap
这个是用来上传文件的。话说当年用 HttpClient 上传个文件老费劲了,一会儿编码不对,一会儿参数错误(也怪那时段位太低吧TT)。。。可是现在不同了,自从有了 Retrofit,妈妈再也不用担心文件上传费劲了~~~
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
public interface FileUploadService { 
     @Multipart
     @POST ( "upload" )
     Call<ResponseBody> upload( @Part ( "description" ) RequestBody description,
                               @Part MultipartBody.Part file);
}

如果你需要上传文件,和我们前面的做法类似,定义一个接口方法,需要注意的是,这个方法不再有 @FormUrlEncoded 这个注解,而换成了 @Multipart,后面只需要在参数中增加 Part 就可以了。也许你会问,这里的 Part 和 Field 究竟有什么区别,其实从功能上讲,无非就是客户端向服务端发起请求携带参数的方式不同,并且前者可以携带的参数类型更加丰富,包括数据流。也正是因为这一点,我们可以通过这种方式来上传文件,下面我们就给出这个接口的使用方法:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//先创建 service
FileUploadService service = retrofit.create(FileUploadService. class );
//构建要上传的文件
File file = new File(filename);
RequestBody requestFile =
         RequestBody.create(MediaType.parse( "application/otcet-stream" ), file);
MultipartBody.Part body =
         MultipartBody.Part.createFormData( "aFile" , file.getName(), requestFile);
String descriptionString = "This is a description" ;
RequestBody description =
         RequestBody.create(
                 MediaType.parse( "multipart/form-data" ), descriptionString);
Call<ResponseBody> call = service.upload(description, body);
call.enqueue( new Callback<ResponseBody>() {
   @Override
   public void onResponse(Call<ResponseBody> call,
                          Response<ResponseBody> response) {
     System.out.println( "success" );
   }
   @Override
   public void onFailure(Call<ResponseBody> call, Throwable t) {
     t.printStackTrace();
   }
});

在实验时,我上传了一个只包含一行文字的文件:
Visit me: http://www.println.net
那么我们去服务端看下我们的请求是什么样的:
HEADERS
Accept-Encoding: gzip
Content-Length: 470
Content-Type: multipart/form-data; boundary=9b670d44-63dc-4a8a-833d-66e45e0156ca
User-Agent: okhttp/3.2.0
X-Request-Id: 9d70e8cc-958b-4f42-b979-4c1fcd474352
Via: 1.1 vegur
Host: requestb.in
Total-Route-Time: 0
Connection: close
Connect-Time: 0

FORM/POST PARAMETERS
description: This is a description

RAW BODY
--9b670d44-63dc-4a8a-833d-66e45e0156ca
Content-Disposition: form-data; name="description"
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 21

This is a description
--9b670d44-63dc-4a8a-833d-66e45e0156ca
Content-Disposition: form-data; name="aFile"; filename="uploadedfile.txt"
Content-Type: application/otcet-stream
Content-Length: 32

Visit me:  http://www.println.net
--9b670d44-63dc-4a8a-833d-66e45e0156ca--

我们看到,我们上传的文件的内容出现在请求当中了。如果你需要上传多个文件,就声明多个 Part 参数,或者试试 PartMap。

1.5 Converter,让你的入参和返回类型丰富起来 1.5.1 RequestBodyConverter
1.4.3 当中,我为大家展示了如何用 Retrofit 上传文件,这个上传的过程其实。。还是有那么点儿不够简练,我们只是要提供一个文件用于上传,可我们前后构造了三个对象:
天哪,肯定是哪里出了问题。实际上,Retrofit 允许我们自己定义入参和返回的类型,不过,如果这些类型比较特别,我们还需要准备相应的 Converter,也正是因为 Converter 的存在, Retrofit 在入参和返回类型上表现得非常灵活。
下面我们把刚才的 Service 代码稍作修改:
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
public interface FileUploadService { 
     @Multipart
     @POST ( "upload" )
     Call<ResponseBody> upload( @Part ( "description" ) RequestBody description,
         //注意这里的参数 "aFile" 之前是在创建 MultipartBody.Part 的时候传入的
         @Part ( "aFile" ) File file);
}

现在我们把入参类型改成了我们熟悉的 File,如果你就这么拿去发请求,服务端收到的结果会让你哭了的。。。
RAW BODY
--7d24e78e-4354-4ed4-9db4-57d799b6efb7
Content-Disposition: form-data; name="description"
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 21

This is a description
--7d24e78e-4354-4ed4-9db4-57d799b6efb7
Content-Disposition: form-data; name="aFile"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 35

// 注意这里!!之前是文件的内容,现在变成了文件的路径
{"path":"samples/uploadedfile.txt"} 
--7d24e78e-4354-4ed4-9db4-57d799b6efb7--

服务端收到了一个文件的路径,它肯定会觉得

好了,不闹了,这明显是 Retrofit 在发现自己收到的实际入参是个 File 时,不知道该怎么办,情急之下给 toString了,而且还是个 JsonString(后来查证原来是使用了 GsonRequestBodyConverter。。)。
接下来我们就自己实现一个 FileRequestBodyConverter,
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
static class FileRequestBodyConverterFactory extends Converter.Factory {
   @Override
   public Converter<File, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
     return new FileRequestBodyConverter();
   }
}
static class FileRequestBodyConverter implements Converter<File, RequestBody> {
   @Override
   public RequestBody convert(File file) throws IOException {
     return RequestBody.create(MediaType.parse( "application/otcet-stream" ), file);
   }
}

在创建 Retrofit 的时候记得配置上它:
[Java]  纯文本查看  复制代码
?
1
addConverterFactory( new FileRequestBodyConverterFactory())

这样,我们的文件内容就能上传了。来,看下结果吧:
RAW BODY
--25258f46-48b0-4a6b-a617-15318c168ed4
Content-Disposition: form-data; name="description"
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 21

This is a description
--25258f46-48b0-4a6b-a617-15318c168ed4
//注意看这里,filename 没了
Content-Disposition: form-data; name="aFile"
//多了这一句
Content-Transfer-Encoding: binary
Content-Type: application/otcet-stream
Content-Length: 32

Visit me:  http://www.println.net
--25258f46-48b0-4a6b-a617-15318c168ed4--

文件内容成功上传了,当然其中还存在一些问题,这个目前直接使用 Retrofit 的 Converter 还做不到,原因主要在于我们没有办法通过 Converter 直接将 File 转换为 MultiPartBody.Part,如果想要做到这一点,我们可以对 Retrofit 的源码稍作修改,这个我们后面再谈。

1.5.2 ResponseBodyConverter
前面我们为大家简单示例了如何自定义 RequestBodyConverter,对应的,Retrofit 也支持自定义 ResponseBodyConverter。
我们再来看下我们定义的接口:
[Java]  纯文本查看  复制代码
?
1
2
3
4
public interface GitHubService {
   @GET ( "users/{user}/repos" )
   Call<List<Repo>> listRepos( @Path ( "user" ) String user);
}

返回值的类型为 List<Repo>,而我们直接拿到的原始返回肯定就是字符串(或者字节流),那么这个返回值类型是怎么来的呢?首先说明的一点是,GitHub 的这个 api 返回的是 Json 字符串,也就是说,我们需要使用 Json 反序列化得到 List<Repo>,这其中用到的其实是 GsonResponseBodyConverter。
问题来了,如果请求得到的 Json 字符串与返回值类型不对应,比如:
接口返回的 Json 字符串:
{"err":0, "content":"This is a content.", "message":"OK"}

返回值类型
class Result{
    int code;//等价于 err
    String body;//等价于 content
    String msg;//等价于 message
}

哇,这时候肯定有人想说,你是不是脑残,偏偏跟服务端对着干?哈哈,我只是示例嘛,而且在生产环境中,你敢保证这种情况不会发生??
这种情况下, Gson 就是再牛逼,也只能默默无语俩眼泪了,它哪儿知道字段的映射关系怎么这么任性啊。好,现在让我们自定义一个 Converter 来解决这个问题吧!
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static class ArbitraryResponseBodyConverterFactory extends Converter.Factory{
   @Override
   public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
     return super .responseBodyConverter(type, annotations, retrofit);
   }
}
static class ArbitraryResponseBodyConverter implements Converter<ResponseBody, Result>{
   @Override
   public Result convert(ResponseBody value) throws IOException {
     RawResult rawResult = new Gson().fromJson(value.string(), RawResult. class );
     Result result = new Result();
     result.body = rawResult.content;
     result.code = rawResult.err;
     result.msg = rawResult.message;
     return result;
   }
}
static class RawResult{
   int err;
   String content;
   String message;
}

当然,别忘了在构造 Retrofit 的时候添加这个 Converter,这样我们就能够愉快的让接口返回 Result 对象了。
注意!!Retrofit 在选择合适的 Converter 时,主要依赖于需要转换的对象类型,在添加 Converter 时,注意 Converter 支持的类型的包含关系以及其顺序。

2、Retrofit 原理剖析
前一个小节我们把 Retrofit 的基本用法和概念介绍了一下,如果你的目标是学会如何使用它,那么下面的内容你可以不用看了。


不过呢,我就知道你不是那种浅尝辄止的人!这一节我们主要把注意力放在 Retrofit 背后的魔法上面~~

2.1 是谁实际上完成了接口请求的处理?
前面讲了这么久,我们始终只看到了我们自己定义的接口,比如:
[Java]  纯文本查看  复制代码
?
1
2
3
4
public interface GitHubService {
   @GET ( "users/{user}/repos" )
   Call<List<Repo>> listRepos( @Path ( "user" ) String user);
}

而真正我使用的时候肯定不能是接口啊,这个神秘的家伙究竟是谁?其实它是 Retrofit 创建的一个代理对象了,这里涉及点儿 Java 的动态代理的知识,直接来看代码:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public <T> T create( final Class<T> service) {
   Utils.validateServiceInterface(service);
   if (validateEagerly) {
     eagerlyValidateMethods(service);
   }
   //这里返回一个 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);
           }
           //DefaultMethod 是 Java 8 的概念,是定义在 interface 当中的有实现的方法
           if (platform.isDefaultMethod(method)) {
             return platform.invokeDefaultMethod(method, service, proxy, args);
           }
           //每一个接口最终实例化成一个 ServiceMethod,并且会缓存
           ServiceMethod serviceMethod = loadServiceMethod(method);
           
           //由此可见 Retrofit 与 OkHttp 完全耦合,不可分割
           OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
           //下面这一句当中会发起请求,并解析服务端返回的结果
           return serviceMethod.callAdapter.adapt(okHttpCall);
         }
       });
}

简单的说,在我们调用 GitHubService.listRepos 时,实际上调用的是这里的 InvocationHandler.invoke 方法~~

2.2 来一发完整的请求处理流程
前面我们已经看到 Retrofit 为我们构造了一个 OkHttpCall ,实际上每一个 OkHttpCall 都对应于一个请求,它主要完成最基础的网络请求,而我们在接口的返回中看到的 Call 默认情况下就是 OkHttpCall 了,如果我们添加了自定义的 callAdapter,那么它就会将 OkHttp 适配成我们需要的返回值,并返回给我们。
先来看下 Call 的接口:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
public interface Call<T> extends Cloneable {
   //同步发起请求
   Response<T> execute() throws IOException;
   //异步发起请求,结果通过回调返回
   void enqueue(Callback<T> callback);
   boolean isExecuted();
   void cancel();
   boolean isCanceled();
   Call<T> clone();
   //返回原始请求
   Request request();
}

我们在使用接口时,大家肯定还记得这一句:
[Java]  纯文本查看  复制代码
?
1
2
Call<List<Repo>> repos = service.listRepos( "octocat" );
List<Repo> data = repos.execute();

这个 repos 其实就是一个 OkHttpCall 实例,execute 就是要发起网络请求。
OkHttpCall.execute
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override public Response<T> execute() throws IOException {
   //这个 call 是真正的 OkHttp 的 call,本质上 OkHttpCall 只是对它做了一层封装
   okhttp3.Call call;
   synchronized ( this ) {
     //处理重复执行的逻辑
     if (executed) throw new IllegalStateException( "Already executed." );
     executed = true ;
     if (creationFailure != null ) {
       if (creationFailure instanceof IOException) {
         throw (IOException) creationFailure;
       } else {
         throw (RuntimeException) creationFailure;
       }
     }
     
     call = rawCall;
     if (call == null ) {
       try {
         call = rawCall = createRawCall();
       } catch (IOException | RuntimeException e) {
         creationFailure = e;
         throw e;
       }
     }
   }
   if (canceled) {
     call.cancel();
   }
   //发起请求,并解析结果
   return parseResponse(call.execute());
}

我们看到 OkHttpCall 其实也是封装了 okhttp3.Call,在这个方法中,我们通过 okhttp3.Call 发起了进攻,额,发起了请求。有关 OkHttp 的内容,我在这里就不再展开了。
parseResponse 主要完成了由 okhttp3.Response 向 retrofit.Response 的转换,同时也处理了对原始返回的解析:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
   ResponseBody rawBody = rawResponse.body();
   //略掉一些代码
   try {
     //在这里完成了原始 Response 的解析,T 就是我们想要的结果,比如 GitHubService.listRepos 的 List<Repo>
     T body = serviceMethod.toResponse(catchingBody);
     return Response.success(body, rawResponse);
   } catch (RuntimeException e) {
     // If the underlying source threw an exception, propagate that rather than indicating it was
     // a runtime exception.
     catchingBody.throwIfCaught();
     throw e;
   }
}

至此,我们就拿到了我们想要的数据~~


2.3 结果适配,你是不是想用 RxJava?
前面我们已经提到过 CallAdapter 的事儿,默认情况下,它并不会对 OkHttpCall 实例做任何处理:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
   static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();
   @Override
   public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
     ... 毫不留情的省略一些代码 ...
     return new CallAdapter<Call<?>>() {
       ... 省略一些代码 ...
       @Override public <R> Call<R> adapt(Call<R> call) {
         //看这里,直接把传入的 call 返回了
         return call;
       }
     };
   }
}

现在的需求是,我想要接入 RxJava,让接口的返回结果改为 Observable:
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
public interface GitHub {
   @GET ( "/repos/{owner}/{repo}/contributors" )
   Observable<List<Contributor>> contributors(
       @Path ( "owner" ) String owner,
       @Path ( "repo" ) String repo);
}

可不可以呢?当然是可以的,只需要提供一个 Adapter,将 OkHttpCall 转换为 Observable 即可呀!Retrofit 的开发者们早就想到了这个问题,并且为我们提供了相应的 Adapter:
RxJavaCallAdapterFactory
我们只需要在构造 Retrofit 时,添加它:
[Java]  纯文本查看  复制代码
?
1
addCallAdapterFactory(RxJavaCallAdapterFactory.create())

这样我们的接口就可以以 RxJava 的方式工作了。
好,歇会儿,抽一袋烟。。。
接着我们搞清楚 RxJavaCallAdapterFactory 是怎么工作的,首先让我们来看下 CallAdapter 的接口:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface CallAdapter<T> {
   /*
    *返回 Http 返回解析后的类型。需要注意的是这个并不是接口的返回类型,
    *而是接口返回类型中的泛型参数的实参。
    */
   Type responseType();
   /*
    * T 是我们需要转换成的接口返回类型,参数 call 其实最初就是 OkHttpCall 的实例
    * 在这里 T 其实是 RxJava 支持的类型,比如 Observable
    */
   <R> T adapt(Call<R> call);
   //我们需要将 Factory 的子类对应的实例在构造 Retrofit 时添加到其中。
   abstract class Factory {
     //根据接口的返回类型(Observable<T>),注解类型等等来判断是否是当前 Adapter 支持的类型,不是则返回null
     public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,
         Retrofit retrofit);
     //获取指定 index 的泛型参数的上限,比如对于 Map<String, ? extends Number>,index为 1 的参数上限是 Number
     protected static Type getParameterUpperBound( int index, ParameterizedType type) {
       return Utils.getParameterUpperBound(index, type);
     }
     /*
      * 获取原始类型,比如 List<String> 返回 List. class ,这里传入的 type 情况可能比较复杂,因此不能直接当做
      * Class 去做判断。这个方法在判断类型是否为支持的类型时经常用到。
     protected static Class<?> getRawType(Type type) {
       return Utils.getRawType(type);
     }
   }
}

代码中做了较为详细的注释,简单来说,我们只需要实现 CallAdapter 类来提供具体的适配逻辑,并实现相应的 Factory,用来将当前的 CallAdapter注册到 Retrofit 当中,并在 Factory.get 方法中根据类型来返回当前的 CallAdapter即可。知道了这些,我们再来看 RxJavaCallAdapterFactory:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public final class RxJavaCallAdapterFactory extends CallAdapter.Factory {
   ... 请叫我省略君,为了省地方,一个都不放过! ...
   @Override
   public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
     //注意下面的代码主要是判断 returnType 是否为 RxJava 支持的类型
     Class<?> rawType = getRawType(returnType);
     String canonicalName = rawType.getCanonicalName();
     boolean isSingle = "rx.Single" .equals(canonicalName);
     boolean isCompletable = "rx.Completable" .equals(canonicalName);
     if (rawType != Observable. class && !isSingle && !isCompletable) {
       return null ;
     }
     ... 这里省略掉的代码主要是根据返回类型获取合适的 Adapter ...
     return callAdapter;
   }
   ... 我又来了,继续略去一些代码 ...
   static final class SimpleCallAdapter implements CallAdapter<Observable<?>> {
     private final Type responseType;
     private final Scheduler scheduler;
     SimpleCallAdapter(Type responseType, Scheduler scheduler) {
       this .responseType = responseType;
       this .scheduler = scheduler;
     }
     @Override public Type responseType() {
  相关解决方案