Retrofit2源码分析

准备知识

我眼中的Java-Type体系(2)对Method解析的时候要用到
在分析源码前有哪些疑问呢?
CallAdapter是什么?
Converter是什么?
private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
保存Converter为什么是一个List?
private List<Converter.Factory> converterFactories = new ArrayList<>();
保存CallAdapter为什么也是一个List?
无论你是不是对上面有疑问,在看完之后相信你都会有答案了。

总结

  1. Retrofit是OKHttp辅助工具
  2. Retrofit本身是一个注解解析器
  3. Retrofit可以和RxJava更容易配合使用
  4. ServiceMethod 的主要作用是解析构建Request 和Response的解析
  5. OkHttpCall 主要作用是实现OKhttp3的调用
  6. CallAdapter 作用Retrofit.Call命令器的转换成自定义的命令器
  7. Converter<?, RequestBody> requestBodyConverter是实现Request自定义的类转换成RequestBody
  8. Converter<ResponseBody, ?> responseBodyConverter是将ResponseBody转换成我们自定义的类

在对源码进行分析之前先看一下Retrofit,类是很少的。



Retrofit使用过程

我们分析Retrofit2,带着设计者实现功能的想法去分析。下面是对1到4点的说明

  1. 构建OKHttpClient 帮我们实现Http请求
  2. 构建Retrofit,保存相关的参数(参数作用后面会提及)
  3. 实现网络请求
  4. 构建Http请求的传递参数和返回参数

在分析Retrofit前我们看一下Okhttp请求


大概的步骤是
1创建一个OKHTTPClient(配置连接时间,读写时间,超时等参数)
2创建一个Request(配置url,head,get,post方法,body等)
3发送请求获得response
4 对response进行解析
这也是一个HTTP请求的过程。
正式开始我们分析的旅程
Retrofit提供一些辅助类让我们更加方便的

我们现在有一个像www.ss.com/xiMing/tt.php发送一个Student ,返回一个Person的需求。(假设我们用Json格式数据传输)如果我们用OKHTTP的方式,我们要把Student转换成json格式,在服务器返回的时候又要把json格式转换成Person。如果我们有多次这样的请求,我们每个都要这样做。Retrofit把这个过程给我们处理了。而之前Retrofit做的,我们不需要关注的,就是我们今天要学习的。
对 @POST("{user}/tt.php")Call<Person> postStudent(@Body Student student,@Path("user") String user);解析最核心的类是ServiceMethod类。

create方法是创建RequestService的实现。创建一个interface的实现,要么用生成工具,要么用反射。作者使用了后者。Proxy.newProxyInstance反射实现了RequestService。真正对
Call<Person> call = requestService.postStudent(new Student(),"xiMing");进行实现的是

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

ServiceMethod 实现的是对@POST("{user}/tt.php")Call<Person> postStudent(@Body Student student,@Path("user") String user)的解析。也可以说Retrofit最核心的功能是如何解析我们自定义的方法
重要的事情说三遍
Retrofit最核心的功能是如何解析我们自定义的方法
Retrofit最核心的功能是如何解析我们自定义的方法
Retrofit最核心的功能是如何解析我们自定义的方法

ServiceMethod 对自定义的方法的解析

public ServiceMethod build() {
  callAdapter = createCallAdapter();
  responseType = callAdapter.responseType();
  responseConverter = createResponseConverter();
  for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation);
  }
  int parameterCount = parameterAnnotationsArray.length;
  parameterHandlers = new ParameterHandler<?>[parameterCount];
  for (int p = 0; p < parameterCount; p++) {
    Type parameterType = parameterTypes[p];
    Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
    parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
  }
  return new ServiceMethod<>(this);
}

0. Factory 和Converter以及CallAdapter

private List<Converter.Factory> converterFactories = new ArrayList<>();
private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
无论是Converter.Factory还是CallAdapter.Factory都是提供一个规则,一个生产Converter或是CallAdapter的规则。
比如CallAdapter.Factory的规则是 public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,Retrofit retrofit);根据returnType的类型去创建不同的Adapter例如ExecutorCallAdapterFactory中就看是不是Call.class,用户也可以根据自己的需求去定义get,来实现CallAdapter的自定义。
Converter或是CallAdapter提供一个处理数据的规则。
类似我们的每条HTTP就是企业不同的工作任务 CallAdapter.Factory相当于HR,CallAdapter相当于员工。不同的http任务有不同type的技能,HR根据不同type找能处理这种type任务的员工。企业通过work指令让具体的CallAdapter(员工)根据自己的技能处理任务。adapterFactories 是表示有一群HR,有些HR能找到会计算机的,有些HR能找到厨师,所以在企业发布任务的时候回问每个HR,如果HR(小明)能找到就把CallAdapter生成,如果不能就返回null(告诉不能)企业在找下一个HR(小芳)

1.CallAdapter加载和构建

CallAdapter是通过CallAdapter.Factory创建的。
我们分析callAdapter = createCallAdapter();获得的是ExecutorCallAdapterFactory创建的CallAdapter
1、CallAdapter的作用
先说XXXCallAdapterFactory的作用,个人感觉提供一个拓展的方法。我们OKhttp提供的是ExecutorCallAdapterFactory(默认的)

Call<Person> postStudent(@Body Student student);

对于RxJava提供的是

 Observable<Person> postStudent(@Body Student student);

同样我们通过addCallAdapterFactory(RxJavaCallAdapterFactory.create())
所以这个addCallAdapter将控制器(Call<XXX>或是Observable<XXX>)变的可以灵活定义了。同时,我们XXCallAdapterFactory提供了一个Get方法下面是ExecutorCallAdapterFactory的get方法的实现。ExecutorCallAdapterFactory只处理Call的,其他类型不处理。
也就是说我们可以同时提供多个CallAdapter每个都处理自己类型的returnType,这就是为什么我们adapterFactories 是用一个List进行保存。


ExecutorCallAdapterFactory.java

2、ExecutorCallAdapterFactory是如何获得的。


private final List<CallAdapter.Factory> adapterFactories;

adapterFactories在Retrofit build的时候已经默认添加了。


对于converterFactories过程类似就不在说明了。
对Converter.Factory中的两个方法说明
1.requestBodyConverter 是在Post中@Body时被调用的。位置在parseParameter() parseParameter是被ServiceMethod.Builder.build()过程中调用的。
2.responseBodyConverter是在ServiceMethod.Builder.build()中。

//对ResponseBody的转换规则
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
  return null;
}
  //在post方法中我们要传RequestBody的转换规则
 public Converter<?, RequestBody> requestBodyConverter(Type type,
    Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
  return null;
}

第二纬度看问题

每个部分注解都是哪些方法解析的

 @POST("{user}/tt.php")
 Call<Person> postStudent(@Body Student student, @Path("user") String user);
@POST(请求方法)的解析过程

关于字段的使用可以参考Retrofit网络请求参数注解
在构建ServiceMethod.build()的时候被解析出来。
注意:
1.这是{user}在这里只被保存起来,不会被替换
2.在http://xxx/api/News?newsId=1其中newsId=1也没有被拼接起来。
也就是说请求的method是已经确定了。但是完整的url并没有被组装起来

 for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation);
  }

private void parseMethodAnnotation(Annotation annotation) {
  if (annotation instanceof DELETE) {
    parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
  } else if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
  } else if (annotation instanceof HEAD) {
    parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
    if (!Void.class.equals(responseType)) {
      throw methodError("HEAD method must use Void as response type.");
    }
  } else if (annotation instanceof PATCH) {
    parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
  } else if (annotation instanceof POST) {
    parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
  } else if (annotation instanceof PUT) {
    parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
  } else if (annotation instanceof OPTIONS) {
    parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
  } else if (annotation instanceof HTTP) {
    HTTP http = (HTTP) annotation;
    parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
  } else if (annotation instanceof retrofit2.http.Headers) {
    String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
    if (headersToParse.length == 0) {
      throw methodError("@Headers annotation is empty.");
    }
    headers = parseHeaders(headersToParse);
  } else if (annotation instanceof Multipart) {
    if (isFormEncoded) {
      throw methodError("Only one encoding annotation is allowed.");
    }
    isMultipart = true;
  } else if (annotation instanceof FormUrlEncoded) {
    if (isMultipart) {
      throw methodError("Only one encoding annotation is allowed.");
    }
    isFormEncoded = true;
  }
}
完整URL的组装过程

这个过程比较复杂
主要是在ServiceMethod build()过程调用了parseParameterAnnotation
这里对ParameterHandler做一个解释:提供一个abstract void apply(RequestBuilder builder, T value),意思是apply的作用是将T中的数据按照需求构建我们的RequestBuilder ,就以@Url为例生成的是ParameterHandler实例是ParameterHandler.RelativeUrl 其中apply的实现是

 @Override void apply(RequestBuilder builder, Object value) {
  builder.setRelativeUrl(value);//将值直接设置为RelativeUrl。RelativeUrl就是相对路径,例如“api/getimage/xxxpng”
}

关于@Url的使用请参考Retrofit2-如何在请求时使用动态URL
关于其他类型(@Path等)的就不一一举例了。
ParameterHandler的另一个解释是保存了类型的解析方法。但是不进行解析。
这些类型包括(@Url,@Path,@Query,@QueryMap,@Header,@HeaderMap,@Field,@FieldMap,@Part)
解析是在OkHttpCall 中 createRawCall()过程中Request request = serviceMethod.toRequest(args);调用了 handlers[p].apply(requestBuilder, args[p]);

/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
    contentType, hasBody, isFormEncoded, isMultipart);

@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

int argumentCount = args != null ? args.length : 0;
if (argumentCount != handlers.length) {
  throw new IllegalArgumentException("Argument count (" + argumentCount
      + ") doesn't match expected count (" + handlers.length + ")");
}

for (int p = 0; p < argumentCount; p++) {
  handlers[p].apply(requestBuilder, args[p]);
}

return requestBuilder.build();

}
同时经过这两步OKhttp需要发送请求设置参数的过程就全部完成了。接下来就是解析Response了
在说解析Response前,我个人的一个感觉。框架做了许多的容器。用来保存需要处理的东西。然后在不同的步骤进行处理。比如toRequest的步骤其实在ServiceMethod build()是可以完全处理完成的。作者这样写可以让处理过程推迟到必要的时候对如果要放弃任务就减少工作量。

总结

对于@POST("{user}/tt.php")Call<Person> postStudent(@Body Student student,@Path("user") String user)的解析大体是两个部分
1 ServiceMethod Builder.build()
2 OkHttpCall中createRawCall()调用serviceMethod.toRequest(args);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容