当前位置: 首页 > news >正文

Android 网络框架——Retrofit源码精析

众所周知,Retrofit是OkHttp的封装,APP对网络交互部分的实现基本上都是RxJava+Retrofit+OkHttp架构(或协程+Retrofit+OkHttp),可以说,Retrofit已经广为人知。本文主要介绍Retrofit主线源码实现机制,及其所采用的设计模式所涉及到的思想等等。

OKHttp 的使用缺陷

这里说的使用缺陷是不那么便利的地方:

1)用户网络请求的接口配置繁琐,尤其是需要配置复杂请求body,请求头,参数的时候;
2)数据解析过程需要用户手动拿到responsbody进行解析,不能复用;
3)无法适配自动进行线程的切换;
4)万一我们的存在嵌套网络请求就会陷入“回调陷阱”。

Retrofit主要解决的问题

在OkHttp使用如此繁琐的情况下,更方便使用的Retrofit应运而生。其所解决的OkHttp的缺陷问题包含两方面:

①请求前:完成统一配置的网络请求头,一致适配请求request。
②结果返回:retrofit完成数据适配、线程切换。

简单使用

(本段文字仅讲述retrofit的基本用法,如果对其用法以及掌握的老铁可以直接跳过,查看源码解析部分。)

1、添加依赖

implementation 'com.squareup.retrofit2:retrofit:2.9.0'  
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

2、定义网络服务接口

添加依赖并同步后,创建一个接口,专门负责定义网络API。这里我们采用接口是用于

调试Github的开放API,接口为:api.github.com/users/octoc… 在请求后会获取Github用户Octocat的Repo列表:

image.gif 复制其数据,用AS的GsonFormat自动生成Javabean:

image.gif 在这里需要注意这个接口的Json数据是以中括号{开头,是一个Json字符串数组,即对应的JavaBean是一个List,这里生成的Repo是这个List中元素对应的数据结构。对此我们可以定义获取此数据接口。

public interface NetService {  @GET ("users/{user}/repos")     //配置Get请求、URL路径  Call<List<Repo>> getRepos(@Path("user") String user);        //指定返回Call<T>对象,这里的T指定网络解析数据后返回的类型  //@Path("user")表示参数user会替换URL路径中的{user}  
}

3、在AndroidManifest.xml中添加网络权限:

<uses-permission android:name="android.permission.INTERNET"/>

4、实现网络服务接口

创建好接口后,接下来就是用Retrofit实现此接口请求逻辑:

Retrofit retrofit = new Retrofit.Builder()  .baseUrl("https://api.github.com")                  //配置URL的基地址  .addConverterFactory(GsonConverterFactory.*create*()) //配置Gson转换器  .build();  
NetService netService = retrofit.create(NetService.class);  //用Retrofit对象返回一个NetService的实现  
Call<List<Repo>> octocat = netService.getRepos("octocat");  //获取Call对象,用该对象的enqueue实现异步请求  
octocat.enqueue(new Callback<List<Repo>>() {  @Override  public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {  for (Repo rp : response.body()){  Log.i(TAG,"get the id:"+(rp.getId()));  //获取数据后打印ID  }  }  @Override  public void onFailure(Call<List<Repo>> call, Throwable t) {  Log.i(TAG,"onFailure:"+(t.toString()));  }  
});

运行后可见打印的日志:

image.gif 至此,完整的一次网络请求成功了。当然,Retrofit还可以进行更多的复杂操作,如配置请求头、请求提、表单提交等,更多方式可参考square.github.io/retrofit/。

源码流程图

下面是调用Retrofit做一次网络请求的代码流程:

image.gif

动态代理

Retrofit源码中使用了动态代理,在看源码之前我们有必要先了解。在某些场景下,我们要用某些功能,但不是直接调用实现类,而是通过代理类来完成的。通过代理,我们可以隐藏实现类的细节,在不修改实现类的基础之上,增加额外的功能等。在日常生活中,代理模式处处可见,例如房屋中介、二手车贩子、代购等等。我们所说的代理,一般是指静态代理,即一个实现类对应一个代理类,彼此一一对应。假如你现在是个大型轮胎制造商,有家汽车公司准备在你这里订购车轮胎,我们用代码实现下:

首先,实现个接口,表示我们要做的事情——造轮胎:

public interface Wheel {  void produce();  
}

然后实现这个接口:

public class WheelMaker implements Wheel{  @Override  public void produce() {  Log.i("proxyDemo","造好并装上车轮");  }  
}

单独的车轮是没有用的,这个时候汽车厂商就来了,要把车轮装上去,汽车厂商实现代码如下:

public class BenZ implements Wheel{  private Wheel wheel;  public BenZ(Wheel wheel){  this.wheel = wheel;  }  @Override  public void produce() {  before();  wheel.produce();  after();  }  private void after() {  Log.i("proxyDemo","BenZ整车组装完毕,可以售卖了");  }  private void before() {  Log.i("proxyDemo","BenZ组装好其他零件,只缺装轮胎了");  }  
}

汽车厂商来了,拿走了轮胎,就下来就是组装了,组装好就可以售卖,然后才有钱回款给你,你公司才能赚钱,代码如下:

Log.*i*("proxyDemo","--------------静态代理-------------");  
Wheel wheel = new WheelMaker();  
BenZ benZ = new BenZ(wheel);  
benZ.produce();

汽车公司拿着你给的轮胎接口后进行组装,然后售卖,代码很简单没必要过多解释了。对应执行后对应输出如下:

image.gif 随着日积月累,你越做越大,军方来找你做坦克的车轮。你咬咬牙,接了这笔订单。由于军方车轮规格要求更严,对应的,你重新安排了一条生产线接口,做特殊轮胎:

public class SpecialWheelMaker implements Wheel{  @Override  public void produce() {  Log.i("proxyDemo","造好特殊车轮并装上-");  }  
}

听说你造好后,军方来人,拿走你的轮胎回去组装并给了你项目回款:

public class Tank implements Wheel{  private SpecialWheelMaker specialWheelMaker;  public Tank(SpecialWheelMaker specialWheelMaker){  this.specialWheelMaker = specialWheelMaker;  }  @Override  public void produce() {  before();  specialWheelMaker.produce();  after();  }  private void after() {  Log.i("proxyDemo","Tank组装完毕,准备上战场了");  }  private void before() {  Log.i("proxyDemo","Tank组装好其他零件,只缺装轮胎了");  }  
}

组装完毕那天,军方邀请你去参观,于是你一边吩咐手下人继续做事,一边前去参观,代码如下:

Log.i("proxyDemo","--------------汽车的静态代理-------------");
Wheel wheel = new WheelMaker();
BenZ benZ = new BenZ(wheel);
benZ.produce();
Log.i("proxyDemo","--------------Tank的静态代理-------------");
SpecialWheelMaker specialWheelMaker = new SpecialWheelMaker();
Tank tank = new Tank(specialWheelMaker);
tank.produce();

对应输出为:

image.gif 你很开心,因为随着这些项目的顺利落地,你的名声越来越大,无数汽车厂商找上门来合作,但你也发现,成本太高(代码臃肿)了。什么别摸我、玛傻拉弟弟轮胎都几乎一样,除了个别细节不一样外,如此为何还要给他们各个品牌之间独立的代理?22世纪了,早就没有中间商赚差价了,于是你自问自答,一套工业流程,能不能代理所有厂商制造代理呢?答案是肯定的。另外,从代码的角度上来看,如果新增一个厂商,就又要新加一个代理类,代码量会不停增加,而在代理类中的before()和after()都是重复的,不能复用。从某种方面来说,这就不属于好代码了。

这里就需要用到动态代理了。动态代理不需要事先创建代理(汽车厂商)类,而是根据需求动态创建。相当于一个工厂流水线,对应不同种类型轮胎产品,不论轮胎类型数量增加多少,生产线只有一个。从代码的角度来说,不会增加汽车厂商类,厂商的公共方法就能得到复用。

首先,我们定义一个动态代理类,需要实现InvocationHandler接口,然后在invoke()中添加相应的逻辑:

public class DynamicProxyWheel implements InvocationHandler {  private Object wheel;   //代理的对象  public DynamicProxyWheel(Object wheel){  this.wheel = wheel;  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  before();  method.invoke(wheel,args);  after();  return null;  }  private void after() {  Log.i("proxyDemo","全部组装完毕,准备交付");  }  private void before() {  Log.i("proxyDemo","先组装好其他部件及系统");  }  
}

接下来,我们使用这个动态代理轮胎来生产各种轮胎了:

public class DynamicProxyWheel implements InvocationHandler {  private Object wheel;   //代理的对象  public DynamicProxyWheel(Object wheel){  this.wheel = wheel;  }  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  before();  method.invoke(wheel,args);  after();  return null;  }  private void after() {  Log.i("proxyDemo","全部组装完毕,准备交付");  }  private void before() {  Log.i("proxyDemo","先组装好其他部件及系统");  }  
}

image.gif 可见,这里少了BenZ和Tank这些厂商(代理)类。这就是动态代理的使用,而Retrofit就是通过动态代理的方式创建各种网络接口的代理。

Builder模式

Retrofit retrofit = new Retrofit.Builder()  .baseUrl("https://api.github.com")                  //配置URL的基地址  .addConverterFactory(GsonConverterFactory.*create*()) //配置Gson转换器  .build();

在声明Retrofit对象的时候,我们可以看到这里应用了一个建造者(构建者)模式,建造者模式的特点是可以讲一个复杂对象的构成和表示分离开来。这里,我们主要关注build()方法的实现:

public Retrofit build() {  if (baseUrl == null) {  throw new IllegalStateException("Base URL required.");  }  //设置Call的工厂类,如果没有设置,则为OkHttpClient对象  okhttp3.Call.Factory callFactory = this.callFactory;  if (callFactory == null) {  callFactory = new OkHttpClient();  }  //执行回调方法的对象,Android里是在主线程执行回调  Executor callbackExecutor = this.callbackExecutor;  if (callbackExecutor == null) {  callbackExecutor = platform.defaultCallbackExecutor();  }  //网络请求适配器工厂集合  // Make a defensive copy of the adapters and add the default Call adapter.  List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);  callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));  //数据转换器工厂类集合,用于解析网络响应  // Make a defensive copy of the converters.  List<Converter.Factory> converterFactories =  new ArrayList<>(  1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());  // Add the built-in converter factory first. This prevents overriding its behavior but also  // ensures correct behavior when using converters that consume all types.  converterFactories.add(new BuiltInConverters());  converterFactories.addAll(this.converterFactories);  converterFactories.addAll(platform.defaultConverterFactories());  return new Retrofit(  callFactory,  baseUrl,  *unmodifiableList*(converterFactories),  *unmodifiableList*(callAdapterFactories),  callbackExecutor,  validateEagerly);  
}

如果这时候是一头雾水,很正常,我们先尝试去理解代码,到最后,我们都会看明白的。 在Build()中,初始化了回调Call的工厂类,网络请求适配器工厂集合,数据转换器工厂集合。可见其中Call的工厂类默认实现为OkHttpClient。默认的CallAdapter.Factory为ExecutorCallAdapterFactory对象。CallAdapter.Factory主要适配接口的返回类型,如我们上述例子的接口:

public interface NetService {  @GET ("users/{user}/repos")  Call<List<Repo>> getRepos(@Path("user") String user);  
}

返回为Call类型,而Call类型则是由ExecutorCallAdapterFactory适配的,如果在创建Retrofit对象的时候指定配置RxJavaCallAdapterFactory:

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

则接口getRepos的返回类型可为RxJava的Observable。

Build()中还初始化了数据转换器工厂类集合,转换器工厂主要负责网络响应的解析,比如我们之前代码中是:

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

这里设置了GsonConverterFactory,我们就可以使用Gson解析网络结果,当然还有其他的工厂,比如PhotoConverterFactory,可以使用PhotoBuf解析网络结果。

build()中值得注意的一行代码是:

image.gif

image.gif 这里限定了默认的网络回调器,通过绑定主线程Looper的Handler强制将网络回调切换到主线程中执行。相比OkHttp3,Retrofit在使用时一个很方便地方就是在execute() 或者 enqueue() 发起请求后的返回结果时,不需要再切换线程,因为此刻,它已经在安卓的UI主线程当中了。

image.gif 这里我们可以总结下这几个成员变量:

serviceMethodCache:这个map的第一个泛型参数Method即是我们的请求方法。S erviceMethod主要代表网络请求接口中方法进行注解之后我们通过解析解析后拿到的对象,与注解中的post、get等方法成对出现,一一对应。serviceMethodCache看名字就知道是缓存,在这里主要是做网络请求相关配置的缓存(之前2.3版本的时候是个LinkedHashMap)。

call Factory:请求网络的OKHttp的工厂,用于“生产”OKHttp的OKHttpClient;

baseUrl:网络请求的url的基地址,与接口参数拼接起来就是个完整的URL;

converterFactories:数据转换器工厂集合,用于生产我们需要的数据转换器(数据转换器:对我们做了网络请求后得到的response进行转换成我们设定的Java对象);

callAdapterFactories:网络请求适配器工厂集合,用于放置我们的网络请求适配器工厂(网络请求适配器:把我们的call对象转换成其他类型);

callbackExecutor:用于执行回调,Android类中默认的网络回调执行器为Main ThreadExecutor,通过绑定主线程Looper的Handler将网络回调推送到主线程执行。(Retrofit当中的网络请求,最终都是通过线程池将我们的handler来进行调配,可以处理我们的主线程和子线程等线程切换)毫无意外,我们处理异步的网络请求就需要用到它;

validateEagerly:一个标记位,表示是否需要立即解析我们接口的方法。

我们继续看Builder(),在这里Builder是Retrofit的静态内部类:

image.gif 同样也有几个成员变量,有两个新参数要了解下:

Platform:表示Retrofit的适配平台(Android,Java8等);

baseUrl:网络请求的URL地址(注意这里是HttpUrl,不是String);

converterFactories,callAdapterFactories,callbackExecutor,validateEagerly作用同上。

这里的构造方法中返回的是Platform.get(),即适配平台:

image.gif

image.gif

baseUrl()

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

在原例子中,第二行就指定baseUrl(),其方法内部主要工作就是对传入的String进行判空和URL转换:

image.gif 注意这里return的是转换好后的HttpUrl类型。

image.gif

image.gif 这里的baseUrl()将原String拆分成多个字符碎片,然后检测最后一个字符是否是以“/”结尾,如果不是则抛出异常:baseUrl不是以“/”结尾。如果是的话才return。

addConverterFactory

addConverterFactory()操作比较简单,添加工厂集合:

image.gif 那么重点就是里面的参数了:

image.gif

image.gif 可见,最后传的还是Gson转换器工厂对象。

addCallAdapterFactory

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

网络请求适配器工厂,所做操作与addConverterFactory()一样,也都是一样的添加操作。

image.gif 我们看RxJavaCallAdapterFactory.create()的实现:

image.gif

image.gif 这个Scheduler就是RxJava中的调度器。也就是说这里的.create()返回了含有Scheduler调度器对象的RxJavaCallAdapterFactory,然后添加到callAdapterFactories集合中。

对象构成

这时候再返回我们最初的build():

image.gif 相信到这里,你已经不再是一头雾水了。一句话概括这段代码的含义:将retrofit类中的所有成员变量配置完毕,完成整个Retrofit对象的构建。

网络请求

在构建好Retrofit对象后,我们就要对其进行网络请求了:

image.gif 在这里,create()用Retrofit对象返回一个NetService的实现,我们观察下其内部实现:

image.gif

image.gif validateServiceInterface()方法名字直译就是否是有效服务接口,方法内部都是对其是否合法接口的一个判断,validateEagerly一个标记位,表示是否需要立即解析我们接口的方法。如果是,就立即解析。这里我们重点看platform.isDefaultMethod()

image.gif 判断有没有Java8的TYPES,且是默认方法。即判断是不是Java8的默认方法。

image.gif 这个方法即判断是否是静态方法。

Java接口默认不许有默认实现,但是Java8开始可以给接口的一些方法写默认实现了;Java接口不允许写静态方法,但Java8开始允许接口里写静态方法了。然而Retrofit是不支持的这些的,即Retrofit不接纳这两种方式为service接口里的方法。

所以这里判断就是要求不是JAVA8的默认方法和静态方法。这时候才到重点的方法来:

image.gif 也就是说validateServiceInterface()做了一系列接口的合规性验证后,最终执行loadServiceMethod()。

在进这个方法之前,我们先跳回原外界方法:

image.gif 下面的代码可以看出来,是一个动态代理。我们观察其主要逻辑,第一个判断,方法对象是否是Object(对象),如果是则直接调用不代理。接着来到return后的判断方法:

如果platform.isDefaultMethod(method),则返回platform.invokeDefaultMethod(method, service, proxy, args),如果不是,则loadServiceMethod(method).invoke(args)。

为了便于理解,上面的三元判断方法等价于:

if(platform.isDefaultMethod(method)){  platform.invokeDefaultMethod(method, service, proxy, args)  
}else{  loadServiceMethod(method).invoke(args);  
}

还是判断是否是默认方法,如果不是,才执行loadServiceMethod(method)。结合上面代码,可看出无论validateServiceInterface(),最后都会执行loadServiceMethod(),可见loadServiceMethod()才是关键中的关键。

loadServiceMethod()

image.gif 这里的synchronized线程同步锁,保证我们的线程安全。

serviceMethod,在这里对应的是接口方法(的封装)。serviceMethodCache在这里是网络请求相关配置的缓存,本质是一个Map,在这里这个Map的key是method,如果能将对应method的value即serviceMethod取出,且不为空则return出去。如果为空,核心代码又变成了:

image.gif 在这里,又去尝试获取serviceMethodCache中的serviceMethod。如果没有得到,即为空的情况下执行ServiceMethod.parseAnnotations():

image.gif 先看①:

image.gif 可以看到这个建造者模式创建了一个完整的method对象(包含了网络请求的所有参数method,baseUrl,httpMethod,headers,contentType,hasBody,isFormEncoded,isMultipart,isKotlinSuspendFunction等,这里不细讲了。)

再看②:

image.gif

image.gif 代码有点多…精简下:

 static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;//是否是Kotlin suspend方法...Annotation[] annotations = method.getAnnotations();//获取method的注解信息...if (!isKotlinSuspendFunction) {return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);} else if (continuationWantsResponse) {//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.return (HttpServiceMethod<ResponseT, ReturnT>)new SuspendForResponse<>(requestFactory,callFactory,responseConverter,(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);} else {//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.return (HttpServiceMethod<ResponseT, ReturnT>)new SuspendForBody<>(requestFactory,callFactory,responseConverter,(CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,continuationBodyNullable);}}

这个类中有几个参数要注意一下:

image.gif

image.gif callFactory是网络请求工厂,用于生产我们的网络请求Call,即OKHttp的call。代表着实际的网络请求。

CallAdapte r表示的是网络请求适配器,主要的把我们的Call请求适配不同的平台,比如RxJava的平台。

responseConverter表示的是数据转换器,其实就是reponse内容转换器,作用把服务器返回的Json数据转换成我们的JavaBean对象。

image.gif 这里的httpMethod主要表示网络请求的HTTP方法,比如GET,POST等等。

image.gif annotations即网络请求方法中的注解,即原我们的代码接口中的@GET等:

image.gif

image.gif parameterTypes即获取我们网络请求接口方法里的类型。 我们回来看其初始化的地方:

image.gif

image.gif

image.gif

image.gif 看到这里,遍历工厂集合,然后通过get()来获得我们需要的CallAdapter。如果没有合适的,就抛出异常。拿到CallAdapter之后,拿到其responseType(返回的接口类型),这个时候我们就根据我们网络请求方法的返回值、注解类型,从我们的retrofit对象中获取这个网络数据适配器返回的数据类型。

image.gif 在这里,我们通过createResponseConverter获取到数据转换器类型。看下这个方法的实现:

image.gif 这里再通过获取到的注解annotation和之前获取到的responseType再从retrofit中获取:

image.gif

public <T> Converter<ResponseBody, T> nextResponseBodyConverter(@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {Objects.requireNonNull(type, "type == null");Objects.requireNonNull(annotations, "annotations == null");int start = converterFactories.indexOf(skipPast) + 1;for (int i = start, count = converterFactories.size(); i < count; i++) {Converter<ResponseBody, ?> converter =converterFactories.get(i).responseBodyConverter(type, annotations, this);if (converter != null) {//noinspection uncheckedreturn (Converter<ResponseBody, T>) converter;}}StringBuilder builder =new StringBuilder("Could not locate ResponseBody converter for ").append(type).append(".\n");if (skipPast != null) {builder.append("  Skipped:");for (int i = 0; i < start; i++) {builder.append("\n   * ").append(converterFactories.get(i).getClass().getName());}builder.append('\n');}builder.append("  Tried:");for (int i = start, count = converterFactories.size(); i < count; i++) {builder.append("\n   * ").append(converterFactories.get(i).getClass().getName());}throw new IllegalArgumentException(builder.toString());
}

源码逻辑流程很像之前的CallAdapter,这里也是遍历converterFactories并从中获取合适的数据转换器工厂,然后再通过responseBodyConverter()获取到相应的数据转换器。(默认是Json转换器,所以这里一般获取到的是JsonResponseConverter,了解即可)这样就完成了整个数据转换器的初始化工作。

image.gif 从接下来的代码中可以看出,if (!isKotlinSuspendFunction)时,即在Java下开发的非协程挂起函数,直接返回其子类CallAdapted<>对象。

image.gif 这里先暂停,我们先看其invoke()(HttpServiceMethod extends ServiceMethod,而ServiceMethod中有抽象方法invoke()):

image.gif 这个adapt是抽象方法,那具体的实现呢?就在上面CallAdapted中(extends HttpServiceMethod,要复写其抽象方法adapt()),其核心代码就一行:

image.gif 在说adapt()之前先说说OkHttpCall:

OkHttpCall

这个OkHttpCall其实就是对OkHttp中的Call的一个封装。

image.gif 同时其构造方法也把callFactory、responseConverter等参数传入。

image.gif 其自身也封装了异步、执行等方法,所以retrofit的网络请求到底还是调用的OKHttp库。

adapt

image.gif

image.gif 我们上面说到,把创建好的OkHttpCall对象传进了callAdapter的adapt()之中,并返回。

image.gif CallAdapter是个接口,本身没有方法实现逻辑。由于这里是动态代理,因此我们要到各实现类中去寻找此方法具体实现,例如RxJavaCallAdapter等:

image.gif 其主要作用就是把我们的一个一个的Retrofit当中的Call转换成其他平台也可以使用的类型。比如在RxJavaCallAdapter中就转换成了Observable,具体转换方式这里就不细说了。

总结下,先回到我们的网络请求代码:

image.gif 这里的NetService是个接口,接口肯定不能直接调用方法,所以是在create()中通过动态代理Proxy.newProxyInstance去进行拦截,然后调用其InvocationHandler中的invoke()来进行实际的操作,然后通过返回的OkHttpCall对象来进行实际的网络请求。

所以这个netService实际上就是通过动态代理返还过来的OkHttpCall对象。而OkHttpCall对象又是对OkHttp的封装,所以这里的.getRepos(“octocat)其实就是通过我们的OkHttp库去请求我们网络然后实行同步和异步的方法。

Retrofit的请求,OKHttp的创建

Retrofit的请求其实也分为两种:

1、同步:OkHttpCall.execute();

2、异步:OkHttpCall.enqueue();

(这里的OkHttpCall是指的create()里返回的OkHttpCall对象)

这里我们先关注我们代码中的异步方法:

image.gif 可见,这个call是个接口,其所对应的实现类是OkHttpCall。

image.gif 在这里获取call的方式要关注这个方法:

image.gif 可以看到,call通过callFactory创建,在原OKHTTP代码中,如果要创建一个call,则代码是这样的:

okHttpClient.newCall(request).enqueue(new Callback());

newCall()中参数是一个OkHttp里的Request对象,Request对象在Retrofit中通过requestFactory.create()创建,创建好后传给callFactory的newCall()并生成okhttp3.call对象。callFactory对象的创建在HttpServiceMethod的parseAnnotations()方法中,上面已经说明,这里不再赘述。

image.gif 其对应回调也在OkHttpCall中,这时不知道为什么,感觉有所遗漏,我们找到HttpServiceMethod的parseAnnotations()中的callFactory;

image.gif

image.gif 原来很早之前,在build()里就说明了Retrofit用来创建OkHttp3的Call的工厂就是OkHttp3的OkHttpClient()。同理,execute()的创建也是类似,这里就不赘述了。此流程一走下来,OkHttp对象创建完毕。

数据解析与回调

image.gif 在call的异步方法里,在得到初始的rawResponse后,有一个parseResponse()的操作:

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {ResponseBody rawBody = rawResponse.body();// Remove the body's source (the only stateful object) so we can pass the response along.rawResponse =rawResponse.newBuilder().body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())).build();int code = rawResponse.code();if (code < 200 || code >= 300) {try {// Buffer the entire body to avoid future I/O.ResponseBody bufferedBody = Utils.buffer(rawBody);return Response.error(bufferedBody, rawResponse);} finally {rawBody.close();}}if (code == 204 || code == 205) {rawBody.close();return Response.success(null, rawResponse);}ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);try {T body = responseConverter.convert(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;}
}

不难看出,在这里,retorfit将OkHttp返回的数据进行解析,然后将返回数据转换后再return,重点在这里:

image.gif 这个responseConverter转换器的功能,就是将OKHttp返回的不易看懂的数据转换为我们自定义的JavaBean,在之前HttpServiceMethod.parseAnnotations()中:

image.gif 前文有讲述,最终调用的地方是

image.gif 遍历converterFactories并从中获取合适的数据转换器工厂,然后再通过responseBodyConverter()获取到相应的数据转换器。(默认是Json转换器,所以这里一般获取到的是JsonResponseConverter,了解即可)我们可以看出responseConverter来自于converterFactories,而在Retrofit的build()中:

image.gif 而这个this.converterFactories赋值的地方在:

image.gif 即我们写的代码中的:

image.gif 也就是说这个最早初始化的Gson转化器最终被OkHttpCall的parseResponse()中的responseConverter.convert(catchingBody);调用。

image.gif 这里解析完毕后,会执行onResponse回调,将OkHttpCall对象和解析数据后的response对象回调出去,对应的我们的代码中的:

image.gif

设计模式

经过上述的源码流程梳理,不难发现Retrofit其实本质上就是一个网络请求框架的封装。实际上的网络请求等功能实现都是交给了OkHttp去完成。在这些代码封装之中,融合了太多的设计模式在里面。

建造者模式

image.gif 这里很明显有个构建者模式,构建者模式将一个复杂对象的构造与它的表示分离,使得建造过程可以创建不同的表示 其优点很明显,这里构建者模式加上链式调用为Retrofit的参数配置增加不少灵活度,进一步增强代码可读性。

外观模式 门面模式

门面模式要求一个子系统的外部与其内部通信必须通过一个统一的对象进行。Retrofit给我们暴露的方法和类不多。核心类就是Retrofit,我们只管配置Retrofit,然后获取接口对象请求数据,设置回调,其他的都是Retrofit框架内部的事情了。这里Retrofit的门面是Retrofit.create() 。这样的代码设计能降低系统耦合度,除了Retrofit,平时开发常用的开源框架Glide也有一个门面,比如Glide.with(xxx)… 。

动态代理

动态代理指的是程序运行的时候创建代理类的方式,代理模式中最重要的就是区分角色: 1、目标接口;2、目标对象;3、代理对象。

Retrofit中则是通过Proxy.newProxyInstance去调用其InvocationHandler中的invoke()来实现动态代理,详情上面已经说明,这里不再赘述。

image.gif

装饰模式

装饰模式是在不改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。Retrofit中用装饰模式的就是ExecutorCallbackCall了。

image.gif

简单说下,enqueue()方法是异步的,当你调用OkHttpCall的enqueue方法,回调的callback在子线程中,如果需要在主线程接受回调,那就要通过Handler转换到主线程上去。ExecutorCallbackCall就是用来干这个事。当然以上是retrofit默认使用的切换线程方式。如果我们指定用rxjava,那就不会用到这个ExecutorCallbackCall而是RxJava的Call了。

也许你会感觉,装饰模式与静态代理模式很像。但这俩者区别也不小:装饰模式里,装饰后的对象还是“我”,只不过装饰完后“我”的功能更加强大;代理模式里对象已经不是“我”了,只不过代理模式可以联系到我而已。

策略模式

策略模式定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。在CallAdapter中可以添加多个CallAdapter.Factory对象,相当于我们封装了多个不同的适配算法CallAdapter.adapt()。上文已经说明,其对象的生成初始化和调用都在HttpServerMethod.parseAnnotations()中:

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(Retrofit retrofit, Method method, RequestFactory requestFactory) {......CallAdapter<ResponseT, ReturnT> callAdapter =createCallAdapter(retrofit, method, adapterType, annotations);Type responseType = callAdapter.responseType();......}......}private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {try {//noinspection uncheckedreturn (CallAdapter<ResponseT, ReturnT>) retrofit.callAdapter(returnType, annotations);} catch (RuntimeException e) { .......}}

然后方法执行到Retrofit对象的callAdapter() -> nextCallAdapter()。

image.gif

image.gif 在的nextCallAdapter()中,根据返回值retrunType类型遍历调用CallAdapter.Factory的get()方法:

image.gif 如果返回的CallAdapter对象不为null则直接返回该对象,此逻辑即印证了CallAdapter.Factory的get()应该是根据retrunType确定是否返回CallAdapter对象(是否选择该种策略)。

适配器模式

适配器模式将一个类的接口变换为客户端期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。Retrofit代码中将大量的设计模式融合在一起,除了策略模式,CallAdapter中还有此模式,

image.gif这段注释似乎就是在说明,将响应类型为{@code R}的{@link Call}修改为{@code T}类型。Retrofit中,CallAdapter就采用了适配器模式为创建访问Call接口提供服务。默认情况下使用默认的 ExecutorCallAdapterFactory 将okhttp3.call转变成为 retroift中的call,如果设定RxJava,则将okhttp3.call转化为 Observable。上述源码有分析,此处不赘述。


如果你对上述中所描述的知识点还不是很清楚的话,推荐你看下 《OKhttp 源码解析》,里面记录的知识点比较详细,有需要的可以 点击这里直接获取!!!里面记录许多Android 相关学习知识点。↓↓↓


Android 技术提升知识点归整

Android 性能调优系列https://qr18.cn/FVlo89
Android 车载学习指南https://qr18.cn/F05ZCM
Android Framework核心知识点笔记https://qr18.cn/AQpN4J
Android 音视频学习笔记https://qr18.cn/Ei3VPD
Jetpack全家桶(含Compose)https://qr18.cn/A0gajp
Kotlin 入门到精进https://qr18.cn/CdjtAF
Flutter 基础到进阶实战https://qr18.cn/DIvKma
Android 八大知识体系https://qr18.cn/CyxarU
Android 中高级面试题锦https://qr18.cn/CKV8OZ

后续如有新知识点,将会持续更新,尽请期待……

相关文章:

Android 网络框架——Retrofit源码精析

众所周知&#xff0c;Retrofit是OkHttp的封装&#xff0c;APP对网络交互部分的实现基本上都是RxJavaRetrofitOkHttp架构&#xff08;或协程RetrofitOkHttp&#xff09;&#xff0c;可以说&#xff0c;Retrofit已经广为人知。本文主要介绍Retrofit主线源码实现机制&#xff0c;及…...

分布式算法 - Snowflake算法

Snowflake&#xff0c;雪花算法是由Twitter开源的分布式ID生成算法&#xff0c;以划分命名空间的方式将 64-bit位分割成多个部分&#xff0c;每个部分代表不同的含义。这种就是将64位划分为不同的段&#xff0c;每段代表不同的涵义&#xff0c;基本就是时间戳、机器ID和序列数。…...

【java web篇】Maven的基本使用以及IDEA 配置Maven

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…...

【蓝桥集训】第七天并查集

作者&#xff1a;指针不指南吗 专栏&#xff1a;Acwing 蓝桥集训每日一题 &#x1f43e;或许会很慢&#xff0c;但是不可以停下来&#x1f43e; 文章目录1.亲戚2.合并集合3.连通块中点的数量有关并查集的知识学习可以移步至—— 【算法】——并查集1.亲戚 或许你并不知道&#…...

【Playwright】扑面而来的Playwright测试框架

在当今快节奏的开发环境中&#xff0c;测试是软件开发的重要组成部分。 Microsoft Playwright 是一种流行的测试自动化框架&#xff0c;允许开发人员为 Web 应用程序编写端到端测试。 Playwright 建立在 Puppeteer 之上&#xff0c;这是另一个流行的测试自动化框架。在这篇博文…...

React(三) ——新、旧生命周期

&#x1f9c1;个人主页&#xff1a;个人主页 ✌支持我 &#xff1a;点赞&#x1f44d;收藏&#x1f33c;关注&#x1f9e1; 文章目录⛳React生命周期&#x1f30b;初始化阶段&#x1f463;运行中阶段&#x1f3d3;销毁阶段&#x1f3eb;新生命周期的替代&#x1f69a;react中性…...

IT男的一次中年破局尝试--出书

一、转战外企 接上回《人到中年——IT男择业感悟》后&#xff0c;自己从大央企去了某知名外企。外企虽然最近几年的日子已经没有10年前的辉煌与滋润&#xff0c;但相对来说&#xff0c;还能勉强找到工作与生活的平衡点。 划重点&#xff0c;35岁上下的人换工作理由&#xf…...

Python 内置函数eval()

Python 内置函数eval() eval(expression, globalsNone, localsNone) 函数用来执行一个字符串表达式&#xff0c;并返回表达式的值。 expression: 字符串表达式。global: 可选&#xff0c;globals必须是一个字典。locals: 可选&#xff0c;locals可以是任何映射对象。 示例 &…...

【ArcGIS Pro二次开发】系列学习笔记,持续更新,记得收藏

一、前言 这个系列是本人的一个学习笔记。 作为一个ArcGIS Pro二次开发的初学者&#xff0c;最困扰的就是无从入手。网上关于ArcGIS Pro二次开发的中文资料极少&#xff0c;官方文档对于我这样的英文苦手又太不友好。 在搜索无果后&#xff0c;决定自已动手&#xff0c;从头…...

EasyRecovery16MAC苹果版本Photo最新版数据恢复软件

无论是在工作学习中&#xff0c;还是在生活中&#xff0c;Word、Excle等办公软件都是大家很常用的。我们在使用电脑的过程中&#xff0c;有时会因自己的误删或电脑故障&#xff0c;从而导致我们所写的文档丢失了。出现这样的大家不要着急&#xff0c;今天小编就给大家推荐一款可…...

Go的string与strings.Builder

Go的string与strings.Builder 文章目录Go的string与strings.Builder一、strings.Builder 的优势二、string类型的值三、与string相比&#xff0c;Builder的优势体现在拼接方面3.1 Builder的拼接&#xff0c;与Builder的自动扩容3.2 手动扩容3.3 Builder 的重用四、strings.Buil…...

8.Spring Security 权限控制

1.简介入门JavaEE和SpringMVC &#xff1a;Spring Security就是通过11个Fliter进行组合管理小Demouser实体类user.type字段&#xff0c;0普通用户&#xff0c;1超级管理员&#xff0c;2版主补全get set tostringimplement UserDetails&#xff0c;重写以下方法// true: 账号未过…...

curl / python+selenium爬取网页信息

Python爬取网页信息 需求: 持续爬取某嵌入式设备配置网页上的状态信息 shell脚本 简单快速, 不用装插件只能爬取静态内容 用curl命令返回整个网页的内容用grep命令抓取其中某些字段结合正则表达式可多样查找但对于动态内容, 比如对某嵌入式设备配置网页上的一条不断更新的信…...

晶体塑性有限元 Abaqus 三维泰森多边形(voronoi模型)插件 V7.0

1 上一版本完整功能介绍&#xff1a; Voronoi晶体插件-6.0版本[新功能介绍] 晶体塑性有限元 Abaqus 三维泰森多边形&#xff08;voronoi模型&#xff09;插件 V6.0 2 新增功能模块 7.0版本新增功能模块包括&#xff1a;柱状晶体模块和分层晶体模块。 2.1 二维柱状晶体模块 …...

CPython解释器性能分析与优化

原文来自微信公众号“编程语言Lab”&#xff1a;CPython 解释器性能分析与优化 搜索关注 “编程语言Lab”公众号&#xff08;HW-PLLab&#xff09;获取更多技术内容&#xff01; 欢迎加入 编程语言社区 SIG-元编程 参与交流讨论&#xff08;加入方式&#xff1a;添加文末小助手…...

Linux 进程:理解进程和pcb

目录一、进程的概念二、CPU分时机制三、并发与并行1.并发2.并行四、pcb的概念一、进程的概念 什么是进程&#xff1f; 进程就是进行中的程序&#xff0c;即运行中的应用程序。比如&#xff1a;电脑上打开的LOL、QQ…… 这些都是一个个的进程。 什么是应用程序&#xff1f; 应用…...

银行数字化转型导师坚鹏:招商银行数字化转型战略研究

招商银行数字化转型战略研究课程背景&#xff1a; 很多银行存在以下问题&#xff1a;不清楚如何制定银行数字化转型战略&#xff1f;不知道其它银行的数字化转型战略是如何演变的&#xff1f; 课程特色&#xff1a;用实战案例解读招商银行数字化转型战略。用独特视角解…...

java 一文讲透面向对象 (20万字博文)

目录 一、前言 二、面向对象程序设计介绍 1.oop三大特性 : 2.面向对象和面向过程的区别 : 3.面向对象思想特点 : 4.面向对象的程序开发 : 三、Java——类与对象 1.java中如何描述一个事物? 2.什么是类? 3.类的五大成员&#xff1a; 4.封装的前提——抽象 : 5.什么是对…...

使用file-selector-button美化原生文件上传

前言 你平时见到的上传文件是下面这样的? 还是下面这种美化过的button样式 还是下面这种复杂的上传组件。 <input type="file" >:只要指定的是type类型的input,打开浏览器就是上面第一种原生的浏览器默认的很丑的样式。 下面的两种是我从ElementUI截的图,…...

0402换元积分法-不定积分

文章目录1 第一类换元法1.1 定理11.2 例题1.2 常见凑微分形式1.2.1常见基本的导数公式的逆运算1.2.2被积函数含有三角函数2 第二类换元法2.1 定理22.2 常见第二换元代换方法2.2.1 三角代换-弦代换2.2.2 三角代换-切代换2.2.3 三角代换-割代换2.2.4 三角代换汇总2.2.5 倒代换2.2…...

信号类型(雷达)——脉冲雷达(三)

系列文章目录 《信号类型&#xff08;雷达通信&#xff09;》 《信号类型&#xff08;雷达&#xff09;——雷达波形认识&#xff08;一&#xff09;》 《信号类型&#xff08;雷达&#xff09;——连续波雷达&#xff08;二&#xff09;》 文章目录 前言 一、相参雷达 1…...

并查集(13张图解)--擒贼先擒王

目录 前言 故事 &#x1f33c;思路 &#x1f33c;总结 &#x1f33c;代码 &#x1f44a;观察过程代码 &#x1f44a;正确代码 &#x1f44a;细节代码 来自《啊哈算法》 前言 刚学了树在优先队列中的应用--堆的实现 那么树还有哪些神奇的用法呢&#xff1f;我们从一…...

Flutter3引用原生播放器-IOS(Swift)篇

前言由于Flutter项目中需要使用到播放器功能&#xff0c;因此对flutter中各种播放器解决方案进行了一番研究和比对&#xff0c;最后决定还是自己通过Plugin的方法去引用原生播放器符合自己的需求&#xff0c;本篇文章会对各种解决方案做一个简单的比较&#xff0c;以及讲解一下…...

【蓝桥杯每日一题】双指针算法

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; 蓝桥杯 &#x1f319;我与杀戮之中绽放&#xff0c;亦如黎明的花…...

拼数(一般贪心)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题号&#xff1a;NC16783 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld 题目描述 设有n个正整…...

LeetCode 热题 C++ 169. 多数元素 10. 正则表达式匹配 155. 最小栈

力扣169 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 示例 1&#xff1a; 输入&#xff1a;nums [3,2,3] 输出&#xff1…...

Clickhouse学习:MergeTree

MergeTree一、MergeTree逻辑存储结构二、MergeTree物理存储结构三、总结一、MergeTree逻辑存储结构 如上图所示,在排序键(CountrID、Date)上做索引,数据会按照这两个字段先后排序ClickHouse是稀疏索引,每隔8192行做一个索引,如(a,1),(a,2),比如想查a,要读取[0,3)之间的内容,稀疏…...

【java基础】包装类,自动装箱和自动拆箱

文章目录基本介绍包装类自动装箱自动拆箱包装类注意事项包装类比较包装器内容不可变基本介绍 有时&#xff0c;需要将int这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。 例如&#xff0c;Integer类对应基本类型int。通常&#xff0c;这些类称为包装器&#…...

Android笔记(二十五):两种sdk热更插件资源加载方案

背景 在研究sdk插件化热更新方式的过程中总结出了两套插件资源加载方案&#xff0c;在此记录下 资源热更方式 方式一&#xff1a;合并所有插件资源 需要解决资源id冲突问题 资源ID值一共4个字段&#xff0c;由三部分组成&#xff1a;PackageIdTypeIdEntryId PackageId&…...

spring框架--全面详解(学习笔记)

目录 1.Spring是什么 2.Spring 框架特点 3.Spring体系结构 4.Spring开发环境搭建 5.spring中IOC和DI 6.Spring中bean的生命周期 7.Spring Bean作用域 8.spring注解开发 9.Spring框架中AOP&#xff08;Aspect Oriented Programming&#xff09; 10.AOP 实现分类 11.A…...