Retrofit 源码一日游

237 阅读11分钟

前言

Retrofit 是 OkHttp 的一个封装,它结合注解+动态代理,适配器模式,实现了可配置的网络请求,数据解析方式和数据返回类型,同时会把异步请求的回调切换到主线程的一个框架。

为什么使用 Retrofit?

为什么要使用 Retrofit 呢,不是已经有 OkHttp 了吗,直接使用不香吗?回忆下 OkHttp 的使用方式

class OkHttpUtil private constructor() {
    private object OkHttpHolder {
        val okHttpUtil = OkHttpUtil()
    }

    companion object {
        fun getInstance(): OkHttpUtil {
            return OkHttpHolder.okHttpUtil
        }
    }

    private var mOkHttpClient: OkHttpClient = OkHttpClient.Builder().build()

    fun enqueue() {
        val request = Request.Builder().url("https://www.wanandroid.com/").build()
        mOkHttpClient.newCall(request).enqueue(object:Callback{
            override fun onResponse(call: Call, response: Response) {
                val result = response.body().toString()
            }

            override fun onFailure(call: Call, e: IOException) {
                
            }
        })
    }

    fun execute(): Response {
        val request = Request.Builder().url("https://www.wanandroid.com/").build()
        return mOkHttpClient.newCall(request).execute()
    }
}

如果直接使用 OkHttp ,面临几个问题,网络请求的 url,请求方式,请求参数统一管理问题,由于 OkHttp 是直接返回的 json 字符串,所以需要处理数据统一解析问题,Callback 返回的还是异步线程,更新 UI 的时候还需要统一切换到 UI 线程.而 Retrofit 正好帮我们解决了这些问题。

动态代理用在哪里,反射不是会耗性能?

我们日常使用 Retrofit 会创建 Retrofit 实例,然后调用 create 方法来获取我们定义的 API 接口

class RetrofitClient {
    companion object {
        fun getInstance(): RetrofitClient {
            return SingletonHolder.INSTANCE
        }
    }

    private object SingletonHolder {
        const val DEFAULT_TIMEOUT = 20
        const val baseUrl = "https://www.wanandroid.com/"
        val INSTANCE = RetrofitClient()
        lateinit var retrofit: Retrofit
    }

    private constructor() : this(SingletonHolder.baseUrl, null)

    constructor(url: String, headers: Map<String, String>?) {
        val okHttpClient = OkHttpClient.Builder()
            .connectTimeout(SingletonHolder.DEFAULT_TIMEOUT.toLong(), TimeUnit.SECONDS)
            .writeTimeout(
                SingletonHolder.DEFAULT_TIMEOUT.toLong(),
                TimeUnit.SECONDS
            )
            .build()
        SingletonHolder.retrofit = Retrofit.Builder()
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl(url)
            .build()
    }

    fun <T> create(service: Class<T>?): T {
        if (service == null) {
            throw RuntimeException("Api service is null!")
        }
        return SingletonHolder.retrofit.create(service)
    }

}

Retrofit 动态代理的使用就在 create 方法里面,简化代码后:

public class Retrofit {
    @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
    public <T> T create(final Class<T> service) {
        return (T)
                Proxy.newProxyInstance(
                        service.getClassLoader(),
                        new Class<?>[] {service},
                        new InvocationHandler() {
                            @Override
                            public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                                    throws Throwable {
                                return loadServiceMethod(method).invoke(args);
                            }
                        });
    }
}

通过这个方法,Retrofit 为自定义的 API 接口生成了一个代理类,我们调用 API 接口方法去网络请求都会来到 invoke 方法里面。

那问题来了,我们调用接口往往是高频的,使用动态代理就会面临反射耗性能问题。反射耗性能体现在 2 个地方,一个是大量反射创建实例时候,还一个是反射方法的调用。通过注释我们可以知道,Retrofit 推荐我们使用单例创建接口,也就是我们开发中尽量减少 API 接口的数量,一般都一个就好了。在数量少的情况下,动态代理反射创建的对象就少,那就和 new 创建对象区别不大了。接着就是反射方法的调用性能问题,那我们就要来看 loadServiceMethod 方法的实现

public class Retrofit {
    private final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();

    ServiceMethod<?> loadServiceMethod(Method method) {
        ServiceMethod<?> result = serviceMethodCache.get(method);
        if (result != null) return result;

        synchronized (serviceMethodCache) {
            result = serviceMethodCache.get(method);
            if (result == null) {
                result = ServiceMethod.parseAnnotations(this, method);
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }
    
}

可以发现,Retrofit 通过 DCL 方式调用 parseAnnotations 方法,然后使用serviceMethodCache 来缓存反射方法的注解解析信息 ServiceMethod 和返回类型。从而减少获取注解信息,返回类型的调用,从而降低反射带来的性能问题。

ServiceMethod 是怎么创建的,有什么用处?

ServiceMethod 是一个抽象类,它有一个继承类 HttpServiceMethod ,里面的 parseAnnotations 就是创建 ServiceMethod 的地方。

public class HttpServiceMethod {
    static <ResponseT, ReturnT> retrofit2.HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
            Retrofit retrofit, Method method, RequestFactory requestFactory) {
        //返回值是否使用了协程,参数是否有Continuation,就知道是不是协程了。
        boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
        //协程返回类型是不是 Response 类型,Retrofit Response 保存着 OkHttp 返回的 Response 体
        boolean continuationWantsResponse = false;
        //给协程用的,返回类型是否可以为 null ,这里为 false
        boolean continuationBodyNullable = false;

        Annotation[] annotations = method.getAnnotations();
        Type adapterType;
        if (isKotlinSuspendFunction) {
            Type[] parameterTypes = method.getGenericParameterTypes();
            //这里获取 Continuation 的泛型,也是协程的返回类型
            /**
             * 看下 kt 的字节码就明白了,如
             * interface MyApi {
             *     suspend fun login():BaseResponse<String>
             * }
             * 对应字节码为
             *  // access flags 0x401
             *   // signature (Lkotlin/coroutines/Continuation<-Lcom/goach/retrofit/code/BaseResponse<Ljava/lang/String;>;>;)Ljava/lang/Object;
             *   // declaration:  login(kotlin.coroutines.Continuation<? super com.goach.retrofit.code.BaseResponse<java.lang.String>>)
             *   public abstract login(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
             *   @Lorg/jetbrains/annotations/Nullable;() // invisible
             *     // annotable parameter count: 1 (visible)
             *     // annotable parameter count: 1 (invisible)
             *     @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
             *     LOCALVARIABLE this Lcom/goach/retrofit/code/MyApi; L0 L1 0
             *
             *   @Lkotlin/Metadata;(mv={1, 4, 2}, bv={1, 0, 3}, k=1, d1={"\u0000\u0016\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\u0010\u000e\n\u0002\u0008\u0002\u0008f\u0018\u00002\u00020\u0001J\u0017\u0010\u0002\u001a\u0008\u0012\u0004\u0012\u00020\u00040\u0003H\u00a6@\u00f8\u0001\u0000\u00a2\u0006\u0002\u0010\u0005\u0082\u0002\u0004\n\u0002\u0008\u0019\u00a8\u0006\u0006"}, d2={"Lcom/goach/retrofit/code/MyApi;", "", "login", "Lcom/goach/retrofit/code/BaseResponse;", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "app_debug"})
             *   // compiled from: SuspendTest.kt
             * }
             */
            Type responseType =
                    Utils.getParameterLowerBound(
                            0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
            if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
                // Unwrap the actual body type from Response<T>.
                //协程返回类型类型 Response<T>
                responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
                continuationWantsResponse = true;
            } else {
                // TODO figure out if type is nullable or not
                // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
                // Find the entry for method
                // Determine if return type is nullable or not
            }
            //responseType 是协程返回的数据类型,由于 Retrofit 默认的是 Call<?> 返回类型,
            // 所以这里为协程创建了一个 Call<responseType> 的类型
            adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
            //这里给协程方法添加一个 SkipCallbackExecutor 注解,有这个注解的时候,后面调用 adapt 时候不会再创建 ExecutorCallbackCall
            annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
        } else {
            adapterType = method.getGenericReturnType();
        }
        //获取配置的 CallAdapter,这里有3种工厂分别创建的不同的 CallAdapter,DefaultCallAdapterFactory 直接 new CallAdapter,RxJava2CallAdapterFactory 对应 RxJava2CallAdapter,
        //CompletableFutureCallAdapterFactory 对应 ResponseCallAdapter,如果不配置,就默认使用的是 DefaultCallAdapterFactory,如果配置了,Builder 配置的放在最前面,所以优先匹配配置的,匹配不到再使用
        //DefaultCallAdapterFactory。另外 CallAdapter 的 adapt 方法就是去 OkHttp 请求的方法了。
        CallAdapter<ResponseT, ReturnT> callAdapter =
                createCallAdapter(retrofit, method, adapterType, annotations);
        Type responseType = callAdapter.responseType();
        if (responseType == okhttp3.Response.class) {
            throw methodError(
                    method,
                    "'"
                            + getRawType(responseType).getName()
                            + "' is not a valid response body type. Did you mean ResponseBody?");
        }
        if (responseType == Response.class) {
            throw methodError(method, "Response must include generic type (e.g., Response<String>)");
        }
        // TODO support Unit for Kotlin?
        if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
            throw methodError(method, "HEAD method must use Void as response type.");
        }
        //获取数据解析的转换器,这里我们一般配置 GsonConverterFactory,获取到 GsonResponseBodyConverter
        Converter<ResponseBody, ResponseT> responseConverter =
                createResponseConverter(retrofit, method, responseType);

        okhttp3.Call.Factory callFactory = retrofit.callFactory;
        if (!isKotlinSuspendFunction) {
            //不是协程方法,创建 CallAdapted,它继承 HttpServiceMethod,
            //这里 requestFactory 是指 RequestFactory,在 ServiceMethod 里面创建的,
            // callFactory 这里默认是 OkHttpClient,在 Retrofit.Builder 会赋值
            return new retrofit2.HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
        } else if (continuationWantsResponse) {
            //返回数据类型为 Call<Response<T>>
            //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
            return (retrofit2.HttpServiceMethod<ResponseT, ReturnT>)
                    new retrofit2.HttpServiceMethod.SuspendForResponse<>(
                            requestFactory,
                            callFactory,
                            responseConverter,
                            (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
        } else {
            // 协程,并且返回类型不是 Call<Response<T>>
            //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
            return (retrofit2.HttpServiceMethod<ResponseT, ReturnT>)
                    new retrofit2.HttpServiceMethod.SuspendForBody<>(
                            requestFactory,
                            callFactory,
                            responseConverter,
                            (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
                            continuationBodyNullable);
        }
    }
}

通过上面代码我们知道,它分为非协程类型和协程类型,协程类型里面又分返回类型是否是 Response 的。不同类型分别对应着不同的 ServiceMethod.

  • 非协程 -> CallAdapted
  • 协程 Response 返回类型 -> SuspendForResponse
  • 协程非 Response 返回类型 -> SuspendForBody

这三种 ServiceMethod 类型的创建,都需要首先创建 RequestFactory,CallFactory,ResponseConverter,CallAdapter。

  • RequestFactory: 创建 OkHttp 的 Request 作用,在 OkHttpCall 的 createRawCall 会调用该 create 方法
  • CallFactory :这里默认是 OkHttpClient,在 Retrofit.Builder 里面赋值的
  • ResponseConverter, 解析数据时候用到,如 GsonConverterFactory 工厂,它里面的 requestBodyConverter 方法可以获取到 GsonRequestBodyConverter
  • CallAdapter 里面的 adapt 方法会去真正调用 OkHttp 的请求方法进行网络请求。

所以 ServiceMethod 类是 Retrofit 核心类,它保存着每个接口的注解,参数,返回数据类型等等配置信息,还有解析数据类型的方法,调用网络请求的方法。

Retrofit 是如何切换回 UI 线程的?

  1. RxJava2CallAdapterFactory

如果配置的是 RxJava2CallAdapterFactory,那么 RxJava2 自带线程切换,无需处理,通过 observableOn 就可以切换回 UI 线程

  1. DefaultCallAdapterFactory

这是我们未配置 CallAdapter 工厂的时候使用的默认工厂,先看下里面 get 方法源码:

public class DefaultCallAdapterFactory {
    @Override
    public @Nullable CallAdapter<?, ?> get(
            Type returnType, Annotation[] annotations, Retrofit retrofit) {
       //...省略
        final Executor executor =
                Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
                        ? null
                        : callbackExecutor;

        return new CallAdapter<Object, Call<?>>() {
            @Override
            public Type responseType() {
                return responseType;
            }

            @Override
            public Call<Object> adapt(Call<Object> call) {
                return executor == null ? call : new retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall<>(executor, call);
            }
        };
    }

   
}

当我们使用的非协程的时候,会发现它创建了 ExecutorCallbackCall。

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
        this.callbackExecutor = callbackExecutor;
        this.delegate = delegate;
    }

    @Override
    public void enqueue(final Callback<T> callback) {
        Objects.requireNonNull(callback, "callback == null");

        delegate.enqueue(
                new Callback<T>() {
                    @Override
                    public void onResponse(Call<T> call, final Response<T> response) {
                        callbackExecutor.execute(
                                () -> {
                                    if (delegate.isCanceled()) {
                                        // Emulate OkHttp's behavior of throwing/delivering an IOException on
                                        // cancellation.
                                        callback.onFailure(retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall.this, new IOException("Canceled"));
                                    } else {
                                        callback.onResponse(retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall.this, response);
                                    }
                                });
                    }

                    @Override
                    public void onFailure(Call<T> call, final Throwable t) {
                        callbackExecutor.execute(() -> callback.onFailure(retrofit2.DefaultCallAdapterFactory.ExecutorCallbackCall.this, t));
                    }
                });
    }
}

那这个 callbackExecutor 是谁呢,在 Retrofit 的 builder 方法会发现它拿的 Platform 的 defaultCallbackExecutor ,在 Android 平台,实现它的是 MainThreadExecutor:

static final class MainThreadExecutor implements Executor {
  private final Handler handler = new Handler(Looper.getMainLooper());

  @Override
  public void execute(Runnable r) {
    handler.post(r);
  }
}

可以发现 DefaultCallAdapterFactory 在非协程的时候就是使用的 Handler 切换到 UI 线程的。

那协程情况下呢?在上面 adapt 方法里面,如果协程,那么它会直接拿到 OkHttp 的 Call,然后调用 OkHttp 的异步方法进行请求,当请求回来恢复就好了,具体见 KotlinExtensions 里面的 await 方法,它在 SuspendForBody 里面的 adapt 方法里面会调用。

suspend fun <T : Any> Call<T>.await(): T {
  return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        if (response.isSuccessful) {
          val body = response.body()
          if (body == null) {
            val invocation = call.request().tag(Invocation::class.java)!!
            val method = invocation.method()
            val e = KotlinNullPointerException("Response from " +
                method.declaringClass.name +
                '.' +
                method.name +
                " was null but response body type was declared as non-null")
            continuation.resumeWithException(e)
          } else {
            continuation.resume(body)
          }
        } else {
          continuation.resumeWithException(HttpException(response))
        }
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}

这个泛型就是协程返回的类型,Call 是 Retrofit 前面帮我们包装的,成功时候调用 resume 就好了,因为协程如果会帮我们通过 Handler 方式切换到 UI 线程。

另外 suspendCancellableCoroutine,这也是协程里面把回调方法转换为协程的一种方法。比如如果用了 RxJava 有些回调在过渡中,那我们处理业务时候也可以使用这种方式来转换成协程来使用。

Retrofit 是如何支持协程的?

在上文 parseAnnotations 方法里面有提到协程的情况,这里总结下

  1. 获取参数 Continuation 的泛型,也是协程返回的数据类型
  2. 创建协程的返回类型为 Call,T 就是数据返回的类型
  3. 协程会使用 DefaultCallAdapterFactory 工厂,并且在 adapt 方法返回Retrofit 的 Call 对象
  4. 根据协程返回类型创建 ServiceMethod,分别为 SuspendForResponse 和 SuspendForBody。它两的区别就是 SuspendForResponse 返回的数据类型包裹一层 Response
  5. 通过 suspendCancellableCoroutine 的 resume 响应协程的返回结果。

Retrofit 是如何解析数据的?

无论是否使用了协程,最终异步网络请求都会来到 OkHttpCall 里面的 enquene 方法,里面会调用 OkHttp 的 Call 里面的 enqueue 方法得到请求结果 Response.

public class OkHttpCall {
    @Override
    public void enqueue(final Callback<T> callback) {
        //省略...
        call.enqueue(
                new okhttp3.Callback() {
                    @Override
                    public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
                        Response<T> response;
                        try {
                            response = parseResponse(rawResponse);
                        } catch (Throwable e) {
                        }
                    }
                });
    }
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    //... 省略
    try {
        T body = responseConverter.convert(catchingBody);
        return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
    }
}
}

这里面会调用 parseResponse。然后调用对应 responseConverter 里面的 convert 方法。如果配置的是 GsonConverterFactory 就会来到 GsonResponseBodyConverter 的 convert 方法

public T convert(ResponseBody value) throws IOException {
  JsonReader jsonReader = gson.newJsonReader(value.charStream());
  try {
    T result = adapter.read(jsonReader);
    if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
      throw new JsonIOException("JSON document was not fully consumed.");
    }
    return result;
  } finally {
    value.close();
  }
}

通过 OkHttp 的 ResponseBody 拿到 Reader。然后通过反序列化为数据类型赋值。那么,为什么这里不直接使用 fromJson 呢?看下 fromJson 源码会发现不同之处

public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
  if (json == null) {
    return null;
  }
  StringReader reader = new StringReader(json);
  T target = (T) fromJson(reader, typeOfT);
  return target;
}
public final Reader charStream() {
  Reader r = reader;
  return r != null ? r : (reader = new BomAwareReader(source(), charset()));
}

Gson 使用的是 StringReader,而通过 OkHttp 获取的是 BomAwareReader,它们的区别是,BomAwareReader 可以识别 BOM 字节码顺序标记具体采用哪种编码格式。还一个区别就是

private static void assertFullConsumption(Object obj, JsonReader reader) {
  try {
    if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) {
      throw new JsonIOException("JSON document was not fully consumed.");
    }
  } catch (MalformedJsonException e) {
    throw new JsonSyntaxException(e);
  } catch (IOException e) {
    throw new JsonIOException(e);
  }
}

Gson 的 fromJson 会 try catch 住 MalformedJsonException 和 IOException 异常,转换为 gson 自己的异常,而 Retrofit 并没有特殊处理这 2 个异常。

Retrofit 是什么时候创建 OkHttp Call 和 Request 的?

Retrofit 会在 create 方法获取到 serviceMethod 后调用 serviceMethod 的 invoke 方法,这个方法里面会创建 OkHttpCall 对象。这里面同样提供了和 OkHttp Call 同名的 enqueue 方法。在这个方法里面

public class OkHttpCall {
    @GuardedBy("this")
    private @Nullable okhttp3.Call rawCall;
    public void enqueue(final Callback<T> callback) {
        synchronized (this) {
            call = rawCall;
            failure = creationFailure;
            if (call == null && failure == null) {
                try {
                    call = rawCall = createRawCall();
                } catch (Throwable t) {
                }
            }
        }
    }
}

通过 createRawCall 方法就会调用 OkHttpClient 的 newCall 方法创建。这里对 Call 进行了判空,也就是一个 OkHttpCall 对应一个 Call 对象,不会出现多次调用 enqueue 方法导致多个 Call 对象。同时也使用了懒加载的方式,在第一次调用接口请求的时候再创建 Call。

private okhttp3.Call createRawCall() throws IOException {
  okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

而 Request 是在 createRawCall 里面调用 RequestFactory 的 create 方法创建的。这样 Retrofit 创建了 Call 和 Request ,可以调用 OkHttp 的 enqueue 方法进行网络请求了。

也就是多次调用一个接口进行请求,就会多次执行 invoke 方法,所以就会创建多个对应的 OkHttpCall,和多个 Request.而 Call 和 Request 在 enqueue 初始化,仅仅只是为了懒加载。

总结

最后总结下,整个流程走下来就是:

image.png

好了,Retrofit 就暂时学习到这里。