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

news/2024/4/19 5:58:06/文章来源:https://blog.csdn.net/m0_64420071/article/details/129231865

众所周知,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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_74543.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在线文档技术-编辑器篇

这是在线文档技术的第二篇文章&#xff0c;本文将对目前市面上所有的主流编辑器和在线文档进行一次深入的剖析和研究&#xff0c;从而使大家对在线文档技术有更深入的了解&#xff0c;也让更多人能够参与其开发与设计中来。 注意&#xff1a;出于对主流文档产品的尊重&#xf…

基础数据结构--线段树(Python版本)

文章目录前言特点操作数据存储updateLazy下移查询实现前言 月末了&#xff0c;划个水&#xff0c;赶一下指标&#xff08;更新一些活跃值&#xff0c;狗头&#xff09; 本文主要是关于线段树的内容。这个线段树的话&#xff0c;主要是适合求解我们一个数组的一些区间的问题&am…

Xcode Developer Document 开发者文档

总目录 iOS开发笔记目录 从一无所知到入门 文章目录IntroDeveloper Documentation 打开方式菜单栏点击 &#xff5c; 快捷键方式另一种打开方式Intro 2016年我在学校学Java的时候&#xff0c;要查某个Java类/方法的用法还得自己手动下载一种.chm格式的开发文档文件&#xff0c…

Oracle-RAC集群主机重启问题分析

问题背景: 在对一套两节点Oracle RAC19.18集群进行部署时&#xff0c;出现启动数据库实例就会出现主机出现重启的情况&#xff0c;检查发现主机重启是由于节点集群被驱逐导致​。 问题: 两节点Oracle RAC19.18集群,启动数据库实例会导致主机出现重启。 问题分析: 主机多次出现…

DFT基本入门介绍

1.什么是DFT&#xff1f;2.为什么要做DFT&#xff1f;3.“测试”与“验证”的区别4.DFT的核心技术1&#xff09;扫描路径设计&#xff08;Scan Design&#xff09;2)内建自测试&#xff08;Bist&#xff09;3)JTAG4)ATPG5.DFT工程师的岗位职责随着芯片的制程越来小(5nm), 芯片的…

【奶奶看了也不会】AI绘画 Mac安装stable-diffusion-webui绘制AI妹子保姆级教程

1.作品图 2.准备工作 目前网上能搜到的stable-diffusion-webui的安装教程都是Window和Mac M1芯片的&#xff0c;而对于因特尔芯片的文章少之又少&#xff0c;这就导致我们还在用老Intel 芯片的Mac本&#xff0c;看着别人生成美女图片只能眼馋。所以小卷这周末折腾了一天&#…

Android 分区和内存监控

Andorid之所以是分区&#xff0c;是因为各自有对应的功能和用途的考量&#xff0c;可以进行单独读写和格式化。Android 设备包含两类分区&#xff1a;一类是启动分区&#xff0c;对启动过程至关重要。一类是用户分区&#xff0c;用于存储与启动无关的信息。启动分区boot 分区一…

数据库之高级查询

注意&#xff1a;第一个包含空&#xff0c;第二句不包含空注意&#xff1a;第二句是错的&#xff0c;聚合函数不能出现在where中。注意&#xff1a;相当于&#xff0c;按照分组属性&#xff0c;求出每个组的聚合函数值&#xff0c;所以肯定不能放单个属性有冲突with rollup是最…

一文带你搞定线程池原理

1.使用线程池的意义何在&#xff1f;项目开发中&#xff0c;为了统一管理线程&#xff0c;并有效精准地进行排错&#xff0c;我们经常要求项目人员统一使用线程池去创建线程。因为我们是在受不了有些人动不动就去创建一个线程&#xff0c;使用的多了以后&#xff0c;一旦报错就…

怎么依靠网络赚钱,网上可以做什么副业

如今&#xff0c;网上赚钱已经成为许多人职业生涯的选择之一。网上有很多可靠的兼职&#xff0c;让你在家里轻松赚钱。今天给大家推荐五份可靠的网上兼职。一、怎样选择可靠的网络兼职可靠的网络兼职一般是指在家通过网络平台完成兼职任务&#xff0c;完成任务后即可获得报酬。…

学习python第一天---前缀和

一、3956.截断数组&#xff08;前缀和&#xff09;二、前缀和&#xff08;前缀和&#xff09;[0]list(map(int,input().split()))三、子矩阵的和&#xff08;前缀和&#xff09;range(1,n1)四、K倍区间&#xff08;前缀和&#xff09;五、激光炸弹&#xff08;前缀和&#xff0…

Spring Cache的使用--快速上手篇

系列文章目录 分页查询–Java项目实战篇 全局异常处理–Java实战项目篇 完善登录功能–过滤器的使用 更多该系列文章请查看我的主页哦 文章目录系列文章目录前言一、Spring Cache介绍二、Spring Cache的使用1. 导入依赖2. 配置信息3. 在启动类上添加注解4. 添加注解4.1 CacheP…

在Angular项目中引入NG-ZORRO

在Angular项目中引入NG-ZORRO1.前置2.安装NG-ZORRO并进行初始化配置3.引入样式4.引入组件1.前置 首先创建一个angular项目&#xff1a;angular创建一个新项目的步骤 这是我项目的结构&#xff1a; 2.安装NG-ZORRO并进行初始化配置 安装NG-ZORRO&#xff1a;cd 到当前项目位…

智能算法实现PID智能车控制系统

智能算法实现PID智能车控制系统可在微信公众号 *高级嵌入式软件* 里回复 *智能算法* 查看完整版文章摘要关键词第一章绪论1.1智能车概述1.2智能PID研究现状1.3本文工作第二章 PID控制简介第三章 内模PID简介3.1 内模PID控制第四章内模智能PID智能车控制系统设计4.1 系统设计4.2…

《MySQL学习》 表中随机取记录的方式

一.初始化测试表 创建表 words CREATE TABLE words ( id int(11) NOT NULL AUTO_INCREMENT, word varchar(64) DEFAULT NULL, PRIMARY KEY (id)) ENGINEInnoDB;插入测试数据 create procedure idata()begin declare i int; set i 0; while i<10000 do insert into words…

第二节类型转换、运算符

类型转换 自动类型转换&#xff1a; 类型小的变量可以赋值给大的类型变量。 表达式的自动类型转换&#xff1a; byte short char在表达式中是当做 int计算的。 强制类型转换&#xff1a; 大类型的变量转化为小类型的变量。 注&#xff1a;浮点型转换为整数是直接丢掉小数部…

尚硅谷nginx基础

nginx1. nginx安装1.1版本区别1.2安装步骤1.3 启动nginx1.4关于防火墙1.5 安装成系统服务1.6 配置nginx环境变量2. nginx基本使用2.1 基本运行原理2.2 nginx配置文件2.2.1 最小配置2.2.1.1 基本配置说明2.3 虚拟主机2.3.1域名、dns、ip地址的关系2.3.2IP地址和DNS地址的区别2.3…

学渣适用版——Transformer理论和代码以及注意力机制attention的学习

参考一篇玩具级别不错的代码和案例 自注意力机制 注意力机制是为了transform打基础。 参考这个自注意力机制的讲解流程很详细&#xff0c; 但是学渣一般不知道 key&#xff0c;query&#xff0c;value是啥。 结合B站和GPT理解 注意力机制是一种常见的神经网络结构&#xff0…

Android安卓中jni封装代码打包为aar

前文【Android安卓中jni与Java之间传递复杂的自定义数据结构】已经介绍jni编译c++代码且已经成功封装成java,但是c++是以源代码形式继承在app中,本文介绍如何将前述jni c++代码以隐藏源代码封装成aar的形式。 1、aar打包 1.1、新建module 按照流程 File -> New Module …

windows服务器实用(4)——使用IIS部署网站

windows服务器实用——IIS部署网站 如果把windows服务器作为web服务器使用&#xff0c;那么在这个服务器上部署网站是必须要做的事。在windows服务器上&#xff0c;我们一般使用IIS部署。 假设此时前端给你一个已经完成的网站让你部署在服务器上&#xff0c;别人可以在浏览器…