开源框架 | Retrofit 源码解析

对于 Retrofit 应该是再熟悉不过了,都知道它是一个网络框架,但是为什么它还要基于 OkHttp 呢?了解 Retrofit 后会发现,它虽是网络框架却不做任何网络请求,网络请求任务都是交给 OkHttp 去执行的。Retrofit 使用动态代理、建造者、工厂、外观、适配器等设计模式,实现了自身的高度解耦以及在实际在网络请求中的高扩展性,例如多种类型 CallAdapter 和 Converter 支持。先来看看 Retrofit 的基本使用:

1. 使用步骤

  • 创建请求的接口类 IService 和请求方法
    设置请求方法类型,动态设置请求的 url、参数
    public interface IService {
        @GET("JakeWharton")
        Call<ResultBean> getResult();
    }
    
  • 创建 Retrofit
      Retrofit RETROFIT_CLIENT = new Retrofit.Builder()
              .baseUrl("https://github.com/")
              .client(OKHttpHolder.OK_HTTP_CLIENT)
              .addConverterFactory(GsonConverterFactory.create()) //数据转换器
              .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //Retrofit配合RxJava使用
              .build();
    
  • 创建请求方法接口类(代理类)对象
    IService service = retrofit.create(IService.class);
    
  • 创建 Call
    Call call1 = service.get();
    Call call2 = service.post();
    
  • 执行请求(同步/异步)
    call.enqueue()/call.execute()
    
  • 处理请求响应 Callback
          call.enqueue(new Callback<ResultBean>() {
              @Override
              public void onResponse(@NonNull Call<ResultBean> call, @NonNull Response<TranslationBean> response) {
                  //请求成功
              }
    
              @Override
              public void onFailure(@NonNull Call<TranslationBean> call, @NonNull Throwable t) {
                  //请求失败
              }
          });
    

2. 源码解析

2.1 Retrofit.Builder

Retrofit 的创建使用了建造者模式,调用 Retorfit.Builder() 时,首先会找到一个 Platform 即代码的执行平台,例如 Android平台、Java平台。

  • Retrofit.Bulider() 构造方法
      public Builder() {
        this(Platform.get());
      }
    
  • Platform.get()

    findPlatform() 方法中实现了平台创建,由于我们是在 Android 平台使用 Retorfit,直接来看看 Android 平台做了些什么处理:

    private static final Platform PLATFORM = findPlatform();
    
    static Platform get() {
      return PLATFORM;
    }
    
    private static Platform findPlatform() {
      try {
        Class.forName("android.os.Build");
        if (Build.VERSION.SDK_INT != 0) {
          return new Android();
        }
      } catch (ClassNotFoundException ignored) {
      }
      try {
        Class.forName("java.util.Optional");
        return new Java8();
      } catch (ClassNotFoundException ignored) {
      }
      return new Platform();
    }
    
  • Android 平台

    有一个内部类 MainThreadExecutor,创建了一个主线程的 Handlerececute() 方法中通过 handler 发送消息,用于将请求的回调切回主线程,在主线程处理响应的数据。

    static class Android extends Platform {
      @Override public Executor defaultCallbackExecutor() {
        return new MainThreadExecutor();
      }
    
      @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
        if (callbackExecutor == null) throw new AssertionError();
        return new ExecutorCallAdapterFactory(callbackExecutor);
      }
    
      static class MainThreadExecutor implements Executor {
        private final Handler handler = new Handler(Looper.getMainLooper());
    
        @Override public void execute(Runnable r) {
          handler.post(r);
        }
      }
    }
    
  • baseUrl(String baseUrl)

    这里的 baseUrl 是我们的主机地址,通常是使用绝对路径,例如请求地址为:https://github.com/square/retrofit,那么 baseUrl 即为 https://github.com/,注意 baseUrl 必须以 / 结尾。

  • client()

    添加一个 OkHttpClient,默认是一个不带任何配置的 OkHttpClient,如果我们想自定义 OkHttpClient 的连接时长、读/写时长以及拦截器时,可以通过 client() 传入我们自定义的 OkHttpClient。

  • addConverterFactory()

    用于添加数据的转换器工厂,实现对象的序列化和反序列化,可以是基本数据类型的转换器工厂,也可以是 Gson、FastJson等;例如GsonConverterFactory,请求时 GsonRequestBodyConverter 将我们自己定义 JavaBean 或者一些基本的数据类型写入 Gson 中并转换成请求体 RequestBody 发送给服务器,响应返回时 GsonResponseBodyConverter 首先将响应体 ResponseBody 转换为 Gson,Gson 再转换为我们需要的 JavaBean 类型,省去了我们自己对数据的解析便于直接提取数据。

  • addCallAdapterFactory()

    用于添加请求适配器工厂,默认请求适配默认使用 OkHttpCall,也可以是 RxJava 的 Observer 或者 Flowable(都是通过请求适配器将 OkHttpCall 转换得来的),Retrofit 中有三种请求适配器工厂类:

    • DefaultCallAdapterFactory
    • ExecutorCallAdapterFactory( 默认的)

      OkHttpCall 转换为 ExecutorCallbackCall,ExecutorCallbackCall 是 DefaultCallAdapterFactory 的内部类实现了 retrofit2.Call 接口;

    • RxJava2CallAdapterFactory

      创建 RxJava2CallAdapter ,将 OkHttpCall 转换为 Observer 或者 Flowable;

    @Override public Object adapt(Call<R> call) {
      ...
      Observable<?> observable;
      if (isFlowable) {
          return observable.toFlowable(BackpressureStrategy.LATEST);
      }
      ...
      return observable;
    }
    

    CallAdapter 的 adapt() 方法的作用是将默认的 OkHttpCall 转换为我们指定的请求类型,比如 RxJavaObserver 类型。

  • build()
2.2 Retrofit
  • create()
    create() 用于动态返回请求接口类的代理类对象,代理类对象调用接口类中的方法时会调用代理类对象的 InvocationHandler 的 invoke() 方法,invoke() 方法的参数 method 即调用的接口方法,数组 args 即接口方法的参数,在这里通过反射我们的接口方法去调用 HTTP 请求方法,反射是在 ServiceMethod 中实现。
    其中Proxy.newProxyInstance() 有三个参数:
    • service.getClassLoader():service 的 ClassLoader,service 就是我们调用 create() 传进来的自定义的接口类;
    • new Class<?>[] { service }:代理类要实现的接口列表,即我们在自定义的接口类中的接口方法;
    • new InvocationHandler(){}:代理类要执行的调用程序,在代理类对象调用接口方法时执行;
  public <T> T create(final Class<T> service) {
    ...//省略内容主要用于检测 service 是否是一个接口类,且不是扩展得来的接口类
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            ...
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  }

重点来看 loadServiceMethod() 方法:

  • loadServiceMethod()
    loadServiceMethod() 用于返回一个 ServieMethod 实例;
    1. 先从缓存 serviceMethodCache 中获取一个 ServiceMethod,serviceMethodCache 是一个 Map,其中 key 为 Method,value 为 ServiceMethod<?,?>,即一个接口方法对应一个 ServiceMethod;
    2. 如果获取到的 result 不为空直接返回,为空则创建一个新的 ServiceMethod 存入缓存中并返回;
  ServiceMethod<?, ?> loadServiceMethod(Method method) {
    ServiceMethod<?, ?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = new ServiceMethod.Builder<>(this, method).build();
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

为什么有一个 serviceMethodCache 缓存我们的 ServiceMethod?
我们都知道反射机制会影响程序执行的效率,而 Retrofit 将一次请求的结果(ServiceMethod)进行缓存,当再次调用同一个请求时,我们就不需要去进行反射获取 HTTP 方法和参数等信息,而是直接使用 ServiceMethod 里上一次请求已经保存的相关请求信息进行一次请求,所以当我们频繁调用同一个请求时,这里的缓存就可以在很大程度上减少反射带来的性能开销。

2.3 ServiceMethod

ServiceMethod 的作用是解析注解,通过反射机制将请求接口类的接口方法调整为 HTTP 调用,调整的依据就是接口方法和方法参数的 注解,所有和请求接口相关的参数、参数类型、回调类型、返回数据类型、请求适配器、数据转换器都保存在 ServiceMethod 中,同时 ServiceMethod 还实现了请求适配和数据转换器的调用,并向 OkHttpCall 提供执行 OkHttp 请求的 okhttp3.Call。

先来看ServiceMethod 内部类 Builder 的构造方法:

  • Builder
    首先保存 Retrofit 和 Method 的对象,接着获取了 Method 的 methodAnnotations 、parameterTypes 、parameterAnnotationsArray 并保存,这三者分别代表方法的注解、方法的参数类型以及方法参数的注解,其中方法的注解和方法参数的注解会在 build() 方法中进行解析。
    Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }
  • Builder.build()
    build() 方法中首先创建了 CallAdapter 对象和 ResponseConverter 对象,然后将我们在 Builder 构造方法里获取到的 methodAnnotations 、parameterAnnotationsArray 通过分别调用 parseMethodAnnotation()、parseParameterAnnotation() 方法对方法的注解和方法参数的注解进行解析,代码中省略的部分都是对各个对象类型合理性的判断,如果不符合规定则抛出异常。
    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      responseType = callAdapter.responseType();
      ...//检测 responseType 不能是 Response.class 和 okhttp3.Response.class,否则抛出异常
      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];
        if (parameterAnnotations == null) {
          throw parameterError(p, "No Retrofit annotation found.");
        }

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
      }
      ...//检测方法参数的注解是否合理

      return new ServiceMethod<>(this);
    }
  • parseMethodAnnotation()
    1. 根据请求方法的不同注解调用 parseHttpMethodAndPath() 方法,获取相对 url 路径;
    2. 如果方法注解中有 retrofit2.http.Headers 调用 parseHeaders() 方法解析 headers;
    3. 方法的注解 MultipartFormUrlEncoded 不能共存,即一个方法的注解中只能有 @Multipart 或 @FormUrlEncoded,共存则抛出异常;
  • parseParameterAnnotation()

    根据不同的方法参数注解通过 ParameterHandler 返回不同的参数处理类并保存在 parameterHandlers 数组中,每一个参数对应一个 ParameterHandler;

  • toCall()

    用于获取 OkHttp 请求的 Call 对象,实现 OkHttp 请求:

    1. 创建 RequestBuilder 对象;
    2. 遍历 parameterHandlers 数组,根据每个参数注解类型的不同依次将参数添加到 RequestBuilder 中;
    3. newCall(),创建一个新的请求调用并返回;
  okhttp3.Call toCall(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart); //1

    @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;
    ...

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

    return callFactory.newCall(requestBuilder.build()); //3
  }
  • toResponse()
    解析响应,将响应体 ResponseBody 传入数据转换器 Convert 的 convert() 方法解析响应(转换为接口类方法中指定的 Java 对象),convert() 方法根据参数类型的不同将 ResponseBody 转换为对应数据类型的数据,Retrofit 中默认实现该响应数据转换的是 BuiltInConverters ,我们在前面使用 Retrofit.Builder.addConvertFactory() 方法添加的转换器工厂类就是用于为这里提供数据转换器的。
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }
  • adapt()
    调用 CallAdapter 的 adapt() 方法,将 OkHttp 请求 Call 转换为我们设置的适配器类型,即我们在 Retrofit.Builder.addCallAdapterFactory() 方法中添加的适配器工厂类,默认是 Call,还可以是 RxJava。
  T adapt(Call<R> call) {
    return callAdapter.adapt(call);
  }

2.4 OkHttpCall

Retrofit本身用一个 OkHttpCall 类负责处理网络请求,而我们在接口中定义需要定义很多种 Call,例如 Call<BizEntity>,或者Flowable<BizEntity>(RxJava的Flowable对象)等,接口里的 Call 和 Retrofit 里的 OkHttpCall 并不一致,所以我们需要用一个 CallAdapter 去做一个适配转换。

Retrofit底层虽然使用了 OkHttpClient 去处理网络请求,但它并没有使用 okhttp3.Call 这个 Call 接口,而是自己又建了一个 retrofit2.Call 接口,OkHttpCall 继承的是 retrofit2.Call,与 okhttp3.Call 只是引用关系,即在 OkHttpCall 内部创建了 okhttp3.Call。
这样的设计符合依赖倒置原则,可以尽可能的与OkHttpClient解耦。

  • enqueue()

    异步请求,使用 okhttp3.Call 执行异步请求,在 okhttp3.Call 的请求回调中调用 parseResponse() 将 okhttp3.Response 转换为 retrofit2.Response,然后将这个 Response 传给 Retrofit 的 Callback,回调里调用了 ExecutorCallbackCall 的 execute(),通过主线程的 Handler 发送消息 post(runnable) 将回调切换到了主线程处理;

  • execute()

    同步请求,和异步请求一样,都是通过 okhttp3.Call 执行同步请求,接着调用 parseResponse() 将 okhttp3.Response 转换为 retrofit2.Response,由于同步请求没有在子线程中执行,不存在线程切换问题,直接返回结果。

  • createRawCall()

    创建一个 OkHttp 的 Call,即 okhttp3.Call,实际上是调用 ServiceMethod 的 toCall()` 创建而来的,由此可见 Retrofit 在整个请求流程中的分工是很明确的,OkHttpCall 就是和 OkHttp 建立连接的桥梁,它的任务就只是进行网络请求和响应回调处理。

  • parseResponse()

    okhttp3.Response 转换为 Retrofit 中的 Response,而要转换成我们想要的数据类型则需要通过 Converter 进行转换。

3. 总结

简单来说 Retrofit 虽然是网络框架,但它其实并没有做任何和网络请求相关的操作,这些都是交给 OkHttp 去完成的,而对于 Retrofit 而言,它被设计出来一定有目的的。

  1. 在实际业务中,我们的请求参数、请求方法、Call类型、数据转换器都可能不同,Retrofit 通过注解标注接口方法和接口方法参数,通过动态代理生成接口类对象,通过工厂模式支持多种类型的 Call 和 数据转换器,这在一定程度上满足了业务需求的多样性需求;
  2. 我们发现,在使用 Retrofit 时代码是极其简洁的,这得益于 Retrofit 使用了建造者模式创建 Retrofit,使用外观模式去实现内部 ServiceMethod 和 OkHttpCall 的创建和使用;
  3. 最后会发现,其实 Retrofit 主要的类就三个,而且它们分工明确,Retrofit 用于保存我们的 CallAdapter 和 Converter 工厂以及 OkHttpClient,ServiceMethod 用于解析方法注解和方法参数并转换为 HTTP 对应的请求方法,它保存了和请求有关的所有信息,最后 OkHttpCall 用于真正的网络请求和回调处理。

最后以一张 Retrofit 的整体流程图结束这次探索之旅:

4. 参考

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