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

news2024/11/24 6:43:01

众所周知,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 unchecked
      return (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 unchecked
      return (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.coloradmin.cn/o/374044.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二叉树的前序遍历-java两种方式-力扣144

一、题目描述给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。示例 1&#xff1a;输入&#xff1a;root [1,null,2,3]输出&#xff1a;[1,2,3]示例 2&#xff1a;输入&#xff1a;root []输出&#xff1a;[]示例 3&#xff1a;输入&#xff1a;root [1]输出…

MySQL进阶(二)

目录 1、视图 1、检查选项 2、视图的更新 3、视图作用 2、存储过程 1、语法 2、变量 1、系统变量 2、用户定义变量 3、局部变量 3、if 4、参数 5、case 6、循环 1、while 2、repeat 3、loop 7、游标、条件处理程序 8、存储函数 3、触发器 4、锁 1、全局锁 2、表级锁 …

AIR系列|板载LED|gpio引脚选择|GPIO|流水灯|LuatOS-SOC接口|官方demo|学习(20-1):GPIO库基础

AIR系列各型号开发板板载LED对应管脚及GPIO控制代码 AIR103&#xff1a; rtos_bsp "AIR103" then -- Air103开发板LED引脚编号--return pin.PB26, pin.PB25, pin.PB24return 42,41,40 AIR105&#xff1a; rtos_bsp "AIR105" then -- Air105开发板LED引…

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), 芯片的…

xxjob分布式任务调度

前言 在工作中使用到了定时任务,通过查找资料选择了xxjob,以下是xxjob的介绍以及基本的使用. xxjob介绍 XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 将调度行为抽象形成“调度中心”公共平台&#xff0c;而平台自身…

vue3+rust个人博客建站日记3-编写主页

内容 绘制了主页的基本布局设置了封装了header栏组件并设置了全局黑夜模式. 选择一个组件库-Naive UI 如果没有设计能力&#xff0c;又想开发出风格统一的前端页面。就一定要选择一个漂亮的组件库。 本次项目选择使用Naive UI&#xff0c;NaivUI库曾被Vue框架作者尤雨溪推荐…

【云原生】k8s核心技术—集群安全机制 Ingress Helm 持久化存储-20230222

文章目录一、k8s集群安全机制1. 概述2. RBAC——基于角色的访问控制二、Ingress三、Helm1. 引入2. 使用功能Helm可以解决哪些问题3. 介绍4. 3个重要概念5. helm 版本变化6. helm安装及配置仓库7. 使用helm快速部署应用8. 自己创建chart9. 实现yaml高效复用四、持久化存储1.nfs—…

ArXiv简介以及论文提交

arXiv网站简介 arXiv是一个收集物理学、数学、计算机科学、生物学与数理经济学的论文预印本的网站。其中arXiv发音同“archive”&#xff0c;因为“X”代表希腊字母 &#xff0c;国际音标为[kai]。它于1991年8月14日成立&#xff0c;现由美国康奈尔大学维护。 ——维基百科 对…

在Angular项目中引入NG-ZORRO

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

微信小程序、小游戏的流量主一般可以赚多少钱?

本篇文章主要科普小程序、小游戏流量主一般赚钱的实际情况&#xff0c;通过在下长期运营的经验汇总而成。 日期&#xff1a;2023年2月26日 作者&#xff1a;任聪聪 小程序、小程序满1000用户后即可开通流量主&#xff0c;但实际上很多人并没有传说中的那种日赚几千的流量收入的…

学习笔记之Vuex(五)

Vuex&#xff08;五&#xff09;Vuex一、什么是Vuex二、Vuex工作原理三、搭建Vuex环境四、求和案例分析4.1 求和案例——vue实现4.2 求和案例——vuex实现&#xff08;五&#xff09;Vuex 一、什么是Vuex 1.概念 在Vue中实现集中式状态&#xff08;数据&#xff09;管理的一…

第二节类型转换、运算符

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

nacos config

https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-config 必须在 bootstrap.properties 配置 nacos server 的地址 data id : 对应一个微服务 namespace : 对应环境 dev prod 默认 public group : 对应一个项目&#xff0c;&#xff0c;默认 DEFUALT_GROUP 当你…

神经网络 线性回归从0开始实现的代码分析 --跟李沐学AI

3.2. 线性回归的从零开始实现 — 动手学深度学习 2.0.0 documentation 分析了好几天才懂,个人水平有限 如果有错请指出 1.导包 %matplotlib inline import random import torch from d2l import torch as d2ldef synthetic_data(w, b, num_examples): #save""&quo…

spring integration使用:消息转换器

系列文章目录 …TODO spring integration开篇&#xff1a;说明 …TODO spring integration使用&#xff1a;消息路由 spring integration使用&#xff1a;消息转换器 spring integration使用&#xff1a;消息转换器系列文章目录前言消息转换器&#xff08;或者叫翻译器&#x…

SQLserver 语句查询当前数据库版本型号

SQL执行语句select serverproperty(productversion) as 产品版本号,serverproperty(productlevel) as 产品层次,serverproperty(edition) as 版本SQL执行历史记录&#xff1a;select version as 版本号select serverproperty(productversion) as 产品版本号,serverproperty(pro…

【Redis】一文搞懂 Redis 中的缓存穿透、缓存击穿、缓存雪崩及其解决方案

一文搞懂 Redis 中的缓存穿透、缓存击穿、缓存雪崩及其解决方案1. 缓存穿透1.1 什么是缓存穿透1.2 缓存穿透的解决方案1.2.1 缓存空对象1.2.2 布隆过滤器布隆过滤器工作原理Redis 使用布隆过滤器2. 缓存击穿1.1 什么是缓存击穿1.2 缓存击穿的解决方案1.2.1 设置热点数据永不过期…

【无限思维画布】制作思维导图第三步,节点移动与编辑

正在为无限词典制作单词思维导图功能&#xff0c;实现无限单词导图&#xff0c;无限思维画布。目前制作到第三步&#xff0c;实现节点移动与编辑&#xff1a; 节点移动与编辑Details 第一步&#xff0c;搜索 github。 一个是比较完善的&#xff0c;基于普通dom&#xff0c;用…

Random(二)什么是伪共享?@sun.misc.Contended注解

目录1.背景简介2.伪共享问题3.问题解决4.JDK使用示例1.背景简介 我们知道&#xff0c;CPU 是不能直接访问内存的&#xff0c;数据都是从高速缓存中加载到寄存器的&#xff0c;高速缓存又有 L1&#xff0c;L2&#xff0c;L3 等层级。在这里&#xff0c;我们先简化这些复杂的层级…

对象创建的过程

对象创建的过程 在语言层面上&#xff0c;创建对象通常仅仅是一个new关键字而已&#xff08;例外&#xff1a;复制、反序列化&#xff09;&#xff1b; 而在虚拟机中&#xff0c;对象的创建又是怎样一个过程呢&#xff1f;&#xff08;文中讨论的对象限于普通Java对象&#xff…