Skip to content

Latest commit

 

History

History
508 lines (395 loc) · 16.9 KB

0015-retrofit.asc

File metadata and controls

508 lines (395 loc) · 16.9 KB

Retrofit2 源码解析

1. 引子

Retrofit 是一个被工程师们广泛接受的网络库。 它在 OkHttp 的基础上, 提供了对开发者更加友好的APIs(最终还是通过 OkHttp 来执行网络请求)。

Retrofit turns your HTTP API into a Java interface.

我们先看一段 OkHttp 官方的示例代码:

// From: https://square.github.io/okhttp/#post-to-a-server
public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

上面的代码是不是朴素无华?一看就懂。

我们再来看看 Retrofit 的示例代码:

  1. 定义Service接口类,跟服务端的Restful APIs一一对应:

    // Refer: https://developer.github.com/v3/
    public interface GitHubService {
        @GET("/users/{user}")
        Call<UserProfile> userProfile(@Path("user") String user);
    
        @GET("users/{user}/repos")
        Call<List<Repo>> listRepos(@Path("user") String user);
    }
  2. 执行网络请求:

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://github.com/gitapi/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();
    
    GitHubService service = retrofit.create(GitHubService.class);
    
    Call<List<Repo>> reposCall = service.listRepos("yongce");
    List<Repo> repos = reposCall.execute().body(); // 执行网络请求
  3. 还支持 RxJava:

    public interface GitHubService {
        @GET("users/{user}/repos")
        Observable<List<Repo>> listRepos(@Path("user") String user);
    }

    也支持Kotlin协程的 supsend 关键字:

    interface GitHubService {
        @GET("users/{user}/repos")
        suspend fun listRepos(@Path("user") user: String): List<Repo>
    }
    
    // Called on main thread
    suspend fun refreshRepos() {
        val retrofit = Retrofit.Builder()
            .baseUrl("https://github.com/gitapi/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        val service: GitHubService = retrofit.create()
        val repos: List<Repo> = service.listRepos("yongce")
        // use 'repos' to update UI directly...
    }

当第一眼看到上面的 Retrofit 示例代码时,是不是一下来了精神,心中的问号越来越多? 大家一定很好奇,这个魔法是怎么实现的,其技术原理如何? 但并不是每个人都愿意去分析源码,不论是因为没有时间,还是因为过于枯燥、耗时、乏味。 本文将为你呈现 Retrofit2 背后的技术原理和实现细节,不论你是仅想知道其技术原理, 还是也想自己去分析源码,都可以从本文有所收获。

本文的源码解析将分为如下几个部分:

  • 技术原理

  • 设计模式

  • 实现细节

1.1. 源码导入

当前 Retrofit2 源码使用 Gradle 来构建,你在 IntelliJ IDEA 或者 Android Studio 中直接导入源码即可。

📎

本文所分析的源码版本如下:

$ git show HEAD
commit 65de04d1da61f9e8124956f6fff8902fc81d05e2
Merge: 9e597f2 56e15f8
Author: Jake Wharton <github@jakewharton.com>
Date:   Wed Jun 17 23:18:03 2020 -0400

    Merge pull request #3424 from clydebarrow/robovm

    Prevent use of Java 8 classes on RoboVM

2. 技术原理

💡
如果之前没有接触过 Retrofit 库,需要先花点时间去看看 Retrofit官方文档 , 并运行一些示例代码直观感受下。

当我们在思考 Retrofit 背后的技术原理时,可能会提出如下一些问题:

  1. 下面这段代码的背后发生了什么?Service接口类的对象是如何被创建出来的?

    GitHubService service = retrofit.create(GitHubService.class);
  2. 下面这段对Service接口类方法的调用,发生了什么事情?返回值似乎跟 OkHttp 的Call作用相似?

    Call<List<Repo>> reposCall = service.listRepos("yongce");
  3. 下面这段代码似乎与OkHttp的Call相似?这里才真正执行网络请求?

    List<Repo> repos = reposCall.execute().body();
  4. Service接口类中的方法返回值还可以是其它的?是如何支持其它类型的?

    Observable<List<Repo>> listRepos(@Path("user") String user);
  5. suspend函数似乎有点不一样?直接执行了网络请求,而不是返回一个类似Call的中间对象? 按照Kotlin suspend函数的实现惯例,应该可以直接在main线程调用而不会阻塞main线程?

    suspend fun listRepos(@Path("user") user: String): List<Repo>
  6. Converter.Factory 和 CallAdapter.Factory 是如何工作的?

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://github.com/gitapi/")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(io()))
        .build();
  7. Retrofit 是如何把网络请求转发给 OkHttp 的?

  8. Retrofit 是如何处理Service接口类中的那些注解的?

  9. Retrofit 整个工作流程是怎样的?主要有哪些步骤?

2.1. 动态代理

我们先来寻找下面代码的答案:

GitHubService service = retrofit.create(GitHubService.class);

当我们去查看 Retrofit 源码时,很快就能发现上面代码背后的真相:

Retrofit类源码片断
  public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              ...省略代码
            });
  }

原来是通过Java动态代理技术来创建Service接口类的对象的。

此时,似乎Retrofit背后的整个蓝图有点轮廓了?

  1. 通过Java动态代理创建出Service接口类对象。

  2. (猜测)在InvocationHandler中处理Service接口类的方法调用时,解析该方法的函数签名和注解, 根据调用传入的实参,构造出OkHttp网络请求所需要的参数(如URL、request body、headers等), 进一步构造出 okhttp3.Request 对象,然后通过 OkHttpClient#newCall() 构造出 okhttp3.Call 对象。

  3. (猜测)根据Service接口类方法的返回类型,对 okhttp3.Call 对象进行处理:

    • 转换为 retrofit2.Call 对象,并返回;

    • 转换为 RxJava 的 Observerable 对象,并返回;

    • 立即在后台开始执行网络请求,并将当前Kotlin协程挂起,等网络请求返回后恢复当前协程;

    • 如何支持其它返回值类型?显然 Retrofit 自身不可能支持所有返回类型。

2.2. Service接口类方法解析

我们继续看 Retrofit 中 InvocationHandler 的实现,其最终是需要调用 ServiceMethod#parseAnnotations() 方法来解析当前Service接口类方法, 并把解析的结果(ServiceMethod对象)保存在缓存中,供下次直接使用。 然后,调用该ServiceMethod对象的 invoke() 方法来最终完成本次InvocationHandler的处理。

ServiceMethod 类非常简单:

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
    ...省略代码
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

从上面的代码可以看到,ServiceMethod类把方法解析的工作委托给了 RequestFactory#parseAnnotations() 和 HttpServiceMethod#parseAnnotations(),而 #invoke() 的实现交给了子类去完成(即 HttpServiceMethod类)。

ServiceMethod只有一个直接子类 HttpServiceMethod

abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> {
  ...省略代码

  @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

  protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);

  ...省略代码
}

在上面的代码可以看到,HttpServiceMethod#invoke() 先构造出一个 OkHttpCall 对象, 然后通过 HttpServiceMethod#adapt() 抽象方法把Call对象转换为Service接口类的返回类型对象。 似乎署光就在眼前!

HttpServiceMethod有3个子类:

  • CallAdapted:支持 retrofit.Call<T>、Java 8的CompletableFuture<T>、RxJava的Obserable等返回类型

  • SuspendForResponse:支持suspend函数,返回类型为 Response<T>

  • SuspendForBody:支持suspend函数,返回类型为 T

2.3. 总结

让我们把前面的技术点串联在一起,看看Retrofit背后的技术原理全貌:

  1. 通过Java动态代理技术,创建出Service接口类对象。

  2. 当该Service接口类对象的方法被调用时(假设返回值类型为ReturnT),

    1. 解析该方法的函数签名及注解,结合Retrofit对象中的baseUrl参数,构建出此HTTP请求的相关参数。 在构建HTTP参数时,会通过Retrofit对象中的Converter.Factory,对参数进行必要的转换(例如,把Java对象转换为JSON对象)。

    2. 使用前面构建出的HTTP参数,进一步构建出OkHttp的Call对象。

    3. 通过Retrofit对象中的CallAdapter.Factory,把OkHttp的Call对象转换为ReturnT类型的对象。

    4. 此时,根据是否是suspend函数或者ReturnT的具体类型,有两种结果:

      • HTTP网络请求立即执行(例如,Kotlin suspend函数,Java 8的CompletableFuture)。

      • HTTP网络请求将由返回的ReturnT对象的某个操作触发(例如,retrofit2.Call、RxJava的Observable)。

  3. 当Service接口类对象的方法调用返回时,有下列情况:

    1. 已经拿到服务器的返回结果(如,Kotlin suspend函数)。

    2. HTTP网络请求已经开始执行,正在等待结果返回的通知(如,Java 8的CompletableFuture)。

    3. HTTP网络请求未开始,由返回对象的进一步操作来触发(如,retrofit2.Call、RxJava的Observable)。

  4. 当HTTP请求结果返回时,也会通过Retrofit对象中的Converter.Factory,对返回结果进行转换 (例如,把JSON字符串转换为Java对象)。

  5. 通过自定义CallAdapter.Factory,并把它的对象添加到Retrofit对象中,即可支持Service接口类方法的自定义返回类型。

  6. 通过自定义Converter.Factory,并把它的对象添加到Retrofit对象中,即可支持自定义类型、注解的类型转换 (Java对象和String、RequestBody、ResponseBody之间的转换)。

3. 设计模式

4. 实现细节

4.1. Retrofit#validateServiceInterface()

在 Retrofit#create() 方法的实现中,会先调用 Retrofit#validateServiceInterface() 去验证传入的Service接口类是否合法。

默认情况下,只会验证Service接口类本身是否合法(包括其继承的interface), 例如,是否是interface,是否有泛型(不允许泛型); 而不会验证Service接口类中的方法是否合法,此时只会在该方法第一次被调用时才进行解析并验证。

可以调用 Retrofit.Builder#validateEagerly() 来强制启用对方法的解析和验证。 但这样做,可能有性能损耗,Retrofit#create()调用耗时也会更长,需要注意一下。

4.2. Kotlin支持

Retrofit 对 Kotlin 的支持主要是指在Service接口类中支持suspend函数, 见前面『引子』部分的示例代码。

要在Service接口类中支持suspend函数,需要解决如下问题:

  • 识别Service接口类中的suspend函数

  • 支持suspend函数的调用

我们知道,suspend函数在编译后,会在函数的参数列表最后增加一个 kotlin.coroutines.Continuation 类型的参数。 Retrofit就是利用这个特点来识别suspend函数的,相应代码在 retrofit2.RequestFactory.Builder#parseParameter() 这个方法中:

if (allowContinuation) {
  try {
    if (Utils.getRawType(parameterType) == Continuation.class) {
      isKotlinSuspendFunction = true;
      return null;
    }
  } catch (NoClassDefFoundError ignored) {
  }
}

在 retrofit2.HttpServiceMethod#parseAnnotations() 方法中, 会根据是否为suspend函数来决定创建哪个 HttpServiceMethod 子类对象:

    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
      return (HttpServiceMethod<ResponseT, ReturnT>)
          new SuspendForResponse<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      return (HttpServiceMethod<ResponseT, ReturnT>)
          new SuspendForBody<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
              continuationBodyNullable);
    }

如果当前Service接口类的方法为suspend函数,那么对其调用会最终

在 KotlinExtensions.kt 文件中,定义了多个扩展函数, 主要供前面的 SuspendForResponse 和 SuspendForBody 这两个类来调用:

inline fun <reified T> Retrofit.create(): T = create(T::class.java)

suspend fun <T : Any> Call<T>.await(): T

suspend fun <T : Any> Call<T?>.await(): T?

suspend fun <T> Call<T>.awaitResponse(): Response<T>
💡
作为 Retrofit 库的使用者,我们应该只会用到 Retrofit.create() 这个内联函数。

4.3. Callback Executor

在构建Retrofit对象时,可以为其指定Callback Executor。如果没有指定,那么:

  • Android平台会使用默认的 MainThreadExecutor(主线程执行回调)

  • 其它平台无Callback Executor

Callback Executor 用于控制 retrofit2.Call#enqueue() 在回调callback参数时在哪个线程执行 (参见 retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall类 的实现)。

4.4. Retrofit添加默认AdapterFactory重复的bug

4.5. Converter

4.5.1. 内置Converters

Retrofit内置的 Converter.Factory 为 retrofit2.BuiltInConverters 。 如果当前平台支持Java 8 API(如Java 8+, Android N+),还有一个额外的 OptionalConverterFactory 。

retrofit2.BuiltInConverters#responseBodyConverter()支持如下类型:

  • okhttp3.ResponseBody(此类型支持 @retrofit2.http.Streaming)

  • Void

  • kotlin.Unit

4.5.2. ScalarsConverterFactory

💡
需要添加库依赖:com.squareup.retrofit2:converter-scalars:${versions.retrofit}

ScalarsConverterFactory 的 #requestBodyConverter() 和 #responseBodyConverter() 支持Java的基础类型(如String, boolean, Boolean, long, Long等)。

4.5.3. 其它Converters

Retrofit官方也提供了对JSON、protobuf、xml等数据格式的支持,需要单独添加库依赖。

4.6. CallAdapter

public interface CallAdapter<R, T> {
    Type responseType();
    T adapt(Call<R> call);
}

CallAdapter 用于将 Call<R> 类型对象转换为 T 类型对象(这个T本身也可以是Call<R>, 见 DefaultCallAdapterFactory 实现的 CallAdapter)。

4.6.1. 内置Adapters

Retrofit内置了两个 CallAdapter.Factory :

  • DefaultCallAdapterFactory:仅支持 retrofit2.Call<T> 返回类型

  • CompletableFutureCallAdapterFactory:有两个 CallAdapter ,分别支持如下返回类型:

    • CompletableFuture<T>

    • CompletableFuture<retrofit2.Response<T>>

4.6.2. 其它Adapters

Retrofit官方也提供了对RxJava的、Guava、Scala等库/语言的支持,需要单独添加库依赖。

4.7. 网络请求发起时机

在Retrofit中,按网络请求发起时机,Service接口类方法可分为两类:

  • Service接口类方法被调用时立即发起网络请求:

    • CompletableFutureCallAdapterFactory 支持的 CompletableFuture<T> 返回类型

    • GuavaCallAdapterFactory 支持的 ListenableFuture<T> 返回类型

    • ScalaCallAdapterFactory 支持的 Future<T> 返回类型

    • kotlin suspend函数

  • Service接口类方法返回对象的某个方法被调用时才会发起网络请求:

    • DefaultCallAdapterFactory 支持的 Call<T> 返回类型

    • RxJava3CallAdapterFactory 等RxJava系列Factory支持的 Observable<T>等返回类型