Retrofit源碼解析

Retrofit的源碼分析將從基本的使用方法入手,分析retrofit的實現方案,以及其中涉及到的一些有趣的技巧。而且建議你們也去github下載一份源碼,跟着本文理一遍基本的流程。java

簡單使用

定義HTTP API

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}複製代碼

建立Retrofit並生成API的實現

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);複製代碼

調用API方法,生成Call

Call<List<Repo>> repos = service.listRepos("octocat");複製代碼

Retrofit的建立

retrofit實例的建立,使用了builder模式,從下面的源碼中能夠看出。android

public static final class Builder {
    Builder(Platform platform) {
        this.platform = platform;
        converterFactories.add(new BuiltInConverters());
    }
    public Builder() {
        // Platform.get()方法能夠用於判斷當前的環境
        this(Platform.get());
    }
    public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      if (httpUrl == null) {
        throw new IllegalArgumentException("Illegal URL: " + baseUrl);
      }
      return baseUrl(httpUrl);
    }

    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();// 新建Client,留到以後newCall什麼的
      }

      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> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }
}複製代碼

這裏除了builder模式之外,還有兩個地方須要關注下,一個是Platform.get()方法。它經過Class.forName獲取類名的方式,來判斷當前的環境是否在Android中,這在以後獲取默認的CallAdapterFactory時候將會用到,對這個方法感興趣的能夠跟過去查看下,這裏就不貼了。另外一個是在build()中建立了OkHttpClientgit

retrofit.create

好玩的地方開始了,咱們先來看看這個方法。github

public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(service);
  }
  // 動態代理,啦啦啦
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
      new InvocationHandler() {
        // platform 能夠分辨出你是在android,仍是java8,又或者別的
        private final Platform platform = Platform.get();
        @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          // If the method is a method from Object then defer to normal invocation.
          // 這裏的invoke,Object方法都走這裏,好比equals、toString、hashCode什麼的
          if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args);
          }
          // java8默認方法,1.8的新特性
          if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args);
          }
          // 這裏是核心代碼了
          ServiceMethod<Object, Object> serviceMethod =
              (ServiceMethod<Object, Object>) loadServiceMethod(method);
          OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.callAdapter.adapt(okHttpCall);
        }
      });
}複製代碼

能夠看出建立API使用了動態代理,根據接口動態生成的代理類,將接口的都轉發給了負責鏈接代理類和委託類的InvocationHandler實例,接口方法也都經過其invoke方法來處理。
invoke方法中,首先會經過Platform.get()方法判斷出當前代碼的執行環境,以後會先把Object和Java8的默認方法進行一個處理,也是在進行後續處理以前進行去噪。其中的關鍵代碼其實就是最後三句,這也是這篇文章將要分析的。json

建立ServiceMethod

ServiceMethod<?, ?> loadServiceMethod(Method method) {
  // 從緩存裏面取出,若是有的話,直接返回好了
  ServiceMethod<?, ?> result = serviceMethodCache.get(method);
  if (result != null) return result;
  synchronized (serviceMethodCache) {
    result = serviceMethodCache.get(method);
    if (result == null) {
      // 爲null的話,解析方法的註解和返回類型、參數的註解he參數類型,新建一個ServiceMethod
      result = new ServiceMethod.Builder<>(this, method).build();// ->
      // 新建的ServiceMethod加到緩存列表裏面
      serviceMethodCache.put(method, result);
    }
  }
  return result;
}複製代碼

首先會嘗試根據方法從緩存中取出ServiceMethod實例,若是沒有,在鎖保護以後,還有再嘗試一次,仍是沒有的狀況下,纔會去建立ServiceMethod。ServiceMethod的建立於Retrofit相似,都是builder模式。ServiceMethod建立的實際流程都放在了最後的build()方法中。api

public ServiceMethod build() {
  callAdapter = createCallAdapter();// ->獲取CallAdapter的實現,通常爲ExecutorCallAdapterFactory.get實現
  responseType = callAdapter.responseType();
  if (responseType == Response.class || responseType == okhttp3.Response.class) {
    throw methodError("'"
        + Utils.getRawType(responseType).getName()
        + "' is not a valid response body type. Did you mean ResponseBody?");
  }
  responseConverter = createResponseConverter();// 響應的轉換工廠,如GsonConverterFactory
  for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation);// 真正解析方法註解的地方來了
  }
  if (httpMethod == null) {
    throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
  }
  if (!hasBody) {// POST方法須要有body或者表單
    if (isMultipart) {
      throw methodError(
          "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
    }
    if (isFormEncoded) {
      throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
          + "request body (e.g., @POST).");
    }
  }
  // 上面是請求方法,下面是請求參數
  int parameterCount = parameterAnnotationsArray.length;
  // ParameterHandler的實現類有不少,包括了各類參數,@Field、@Query等
  parameterHandlers = new ParameterHandler<?>[parameterCount];
  for (int p = 0; p < parameterCount; p++) {
    Type parameterType = parameterTypes[p];// 參數類型
    // 和以前同樣的泛型、通配符檢查
    if (Utils.hasUnresolvableType(parameterType)) {
      throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
          parameterType);
    }
    Annotation[] parameterAnnotations = parameterAnnotationsArray[p];// 參數的註解集合
    if (parameterAnnotations == null) {
      throw parameterError(p, "No Retrofit annotation found.");
    }
    // 生成了對應的參數註解ParameterHandler實例
    parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
  }
  // 對方法的一些檢測
  ...

  return new ServiceMethod<>(this);
}複製代碼

能夠看到在build方法中,對CallAdapterConverter進行了建立,這裏跟蹤以後將會回到retrofit類中,在其中將會獲取對應列表中的第一個!null對象,以後將會對API的方法和參數註解進行解析。緩存

註解的解析

CallAdapterConverter等到後面再分析,這裏先看看parseMethodAnnotation(annotation),功能和其名字同樣,其對方法註解進行了解析。架構

/** * 解析方法註解,嗚啦啦 * 經過判斷註解類型來解析 * @param annotation */
private void parseMethodAnnotation(Annotation annotation) {
  if (annotation instanceof DELETE) {
    parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
  } else if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
  } 
  // 其餘的一些方法註解的解析
  ...
}

private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
  if (this.httpMethod != null) {// 已經賦值過了
    throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
        this.httpMethod, httpMethod);
  }
  this.httpMethod = httpMethod;
  this.hasBody = hasBody;
  // value爲設置註解方法時候,設置的值,官方例子中的users/{user}/repos or user
  if (value.isEmpty()) {
    return;
  }
  // 查詢條件的一些判斷
    ...
  this.relativeUrl = value;
  this.relativeUrlParamNames = parsePathParameters(value);
}
`複製代碼

在解析註解時,先經過instanceof判斷出註解的類型,以後調用parseHttpMethodAndPath方法解析註解參數值,並設置httpMethod、relativeUrl、relativeUrlParamNames等屬性。
異步

上面說了API中方法註解的解析,如今來看看方法參數註解的解析,這是經過調用parseParameterAnnotation方法生成ParameterHandler實例來實現的,代碼比較多,這裏挑選@Query來看看。ide

else if (annotation instanceof Query) {
Query query = (Query) annotation;
String name = query.value();
boolean encoded = query.encoded();

Class<?> rawParameterType = Utils.getRawType(type);// 返回基礎的類
gotQuery = true;
// 能夠迭代,Collection
if (Iterable.class.isAssignableFrom(rawParameterType)) {
  if (!(type instanceof ParameterizedType)) {
    throw parameterError(p, rawParameterType.getSimpleName()
        + " must include generic type (e.g., "
        + rawParameterType.getSimpleName()
        + "<String>)");
  }
  ParameterizedType parameterizedType = (ParameterizedType) type;
  Type iterableType = Utils.getParameterUpperBound(0, parameterizedType);// 返回基本類型
  Converter<?, String> converter =
      retrofit.stringConverter(iterableType, annotations);
  return new ParameterHandler.Query<>(name, converter, encoded).iterable();
} else if (rawParameterType.isArray()) {// Array
  Class<?> arrayComponentType = boxIfPrimitive(rawParameterType.getComponentType());// 若是是基本類型,自動裝箱
  Converter<?, String> converter =
      retrofit.stringConverter(arrayComponentType, annotations);
  return new ParameterHandler.Query<>(name, converter, encoded).array();
} else {// Other
  Converter<?, String> converter =
      retrofit.stringConverter(type, annotations);
  return new ParameterHandler.Query<>(name, converter, encoded);
}複製代碼

在@Query中,將分紅Collection、array、other三種狀況處理參數,以後根據這些參數,調用ParameterHandler中的Query靜態類,建立出一個ParameterHandler實例。這樣循環直到解析了全部的參數註解,組合成爲全局變量parameterHandlers,以後構建請求時會用到。

OkHttpCall

ServiceMethod建立完成以後,咱們來看看下一行代碼中的OkHttpCall類,裏面的包含了請求的執行和響應處理,咱們來看看異步請求的作法。

OkHttpCall(ServiceMethod<T, ?> serviceMethod, Object[] args) {
  this.serviceMethod = serviceMethod;
  this.args = args;
}

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

okhttp3.Call call;
Throwable failure;

synchronized (this) {
  if (executed) throw new IllegalStateException("Already executed.");
  executed = true;

  call = rawCall;
  failure = creationFailure;
  if (call == null && failure == null) {
    try {
      call = rawCall = createRawCall();// 建立OkHttp3.Call
    } catch (Throwable t) {
      failure = creationFailure = t;
    }
  }
}

if (failure != null) {
  callback.onFailure(this, failure);
  return;
}

if (canceled) {
  call.cancel();
}

call.enqueue(new okhttp3.Callback() {
  @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) throws IOException {
    Response<T> response;
    try {
      response = parseResponse(rawResponse);// ->
    } catch (Throwable e) {
      callFailure(e);
      return;
    }
    callSuccess(response);
  }

  @Override public void onFailure(okhttp3.Call call, IOException e) {
    try {
      callback.onFailure(OkHttpCall.this, e);
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }

  private void callFailure(Throwable e) {
    try {
      callback.onFailure(OkHttpCall.this, e);
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }

  private void callSuccess(Response<T> response) {
    try {
      callback.onResponse(OkHttpCall.this, response);
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }
});
}


private okhttp3.Call createRawCall() throws IOException {
  Request request = serviceMethod.toRequest(args);// 根據ParameterHandler組裝Request.Builder,生成Request
  okhttp3.Call call = serviceMethod.callFactory.newCall(request);// Retrofit中建立的new OkHttpClient().newCall(request)
  ...
  return call;
}複製代碼

首先在構造函數中傳入了以前新建的serviceMethod和動態代理invoke方法傳遞來的args參數。咱們來看看其異步方法enqueue,將會調用createRawCall()方法,跟進來能夠看到,作了兩件事情,第一件事情,調用serviceMethod.toRequest方法,創造出一個Request對象,這個Request對象就是根據以前提到的方法參數註解的集合parameterHandlers建立的。第二件事是建立一個okhttp3.Call對象,咱們都知道Okhttp中建立這個對象的方法就是newCall,這和上面的代碼一模一樣,那麼callFactory參數是否是就是OkHttpClient呢?bingo!確實如此,稍微跟蹤一下就能夠發現,它的建立出如今Retrofit.Builder.build()方法中,而參數就使用剛剛建立的request對象,構成okhttp3.Call,並返回。

CallAdapter

如今來看看enqueue傳入的參數callback,這個參數可能和不少人心中想的並不同,它並非用戶在使用時傳入的那個Callback對象。那麼他是從哪裏來的呢?不知道你還記不記得我以前在Retrofit.Builder.build()方法中提到過一句代碼Platform.get()。在不使用addCallAdapterFactory的狀況下。將會使用Platform的一種內部類,在Android環境下將會使用到Android類(這實際上是個策略模式)。

static class Android extends Platform {
  @Override public Executor defaultCallbackExecutor() {
    return new MainThreadExecutor();
  }
  @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
    return new ExecutorCallAdapterFactory(callbackExecutor);
  }
  static class MainThreadExecutor implements Executor {
    // Looper.getMainLooper()就是爲嘛響應會在主線程的緣由
    private final Handler handler = new Handler(Looper.getMainLooper());
    @Override public void execute(Runnable r) {
      handler.post(r);
    }
  }
}複製代碼

上面的代碼先稍微放一下,咱們繼續看retrofit.Bulider.build,其中有幾句比較關鍵的代碼。

callFactory = new OkHttpClient();
callbackExecutor = platform.defaultCallbackExecutor();
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));複製代碼

結合Android類中的代碼能夠看出,其最後生成了ExecutorCallAdapterFactory類。雖然看到了CallAdapter.Factory,可是究竟是哪裏執行了enqueue方法呢?如今咱們來看看retrofit.create的最後一句代碼serviceMethod.callAdapter.adapt(okHttpCall)

這裏的callAdapter在不使用addCallAdapterFactory的Android環境中,就是上面咱們說到new ExecutorCallAdapterFactory中get方法返回的對象。

@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
  if (getRawType(returnType) != Call.class) {
    return null;
  }
  final Type responseType = Utils.getCallResponseType(returnType);
  return new CallAdapter<Object, Call<?>>() {
    @Override public Type responseType() {
      return responseType;
    }
    @Override public Call<Object> adapt(Call<Object> call) {// Retrofit動態代理serviceMethod.callAdapter.adapt(okHttpCall);調用到這裏
      return new ExecutorCallbackCall<>(callbackExecutor, call);
    }
  };
}複製代碼

responseType方法返回的對象以後會在Converter中用到,不過接下來先繼續看看其調用adapter方法生成的ExecutorCallbackCall對象。

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

@Override public void enqueue(final Callback<T> callback) {
  checkNotNull(callback, "callback == null");
  delegate.enqueue(new Callback<T>() {
    @Override public void onResponse(Call<T> call, final Response<T> response) {
      callbackExecutor.execute(new Runnable() {
        @Override public void run() {
          if (delegate.isCanceled()) {
            // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
            callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
          } else {
            callback.onResponse(ExecutorCallbackCall.this, response);
          }
        }
      });
    }
    @Override public void onFailure(Call<T> call, final Throwable t) {
      callbackExecutor.execute(new Runnable() {
        @Override public void run() {
          callback.onFailure(ExecutorCallbackCall.this, t);
        }
      });
    }
  });
}複製代碼

這裏的參數callback纔是用戶輸入的回調對象,而其中的delegate就是以前的okhttpCall。因此delegate.enqueue就是調用了OkhttpCall.enqueue,而其中的callbackExecutor就是剛剛的主線程。

順便再來看看經常使用的RxJava2CallAdapter,這裏直接從RxJava2CallAdapter.adapter方法開始

@Override public Object adapt(Call<R> call) {
  Observable<Response<R>> responseObservable = isAsync
      ? new CallEnqueueObservable<>(call)
      : new CallExecuteObservable<>(call);
  Observable<?> observable;
  if (isResult) {
    observable = new ResultObservable<>(responseObservable);
  } else if (isBody) {
    observable = new BodyObservable<>(responseObservable);
  } else {
    observable = responseObservable;
  }
  if (scheduler != null) {
    observable = observable.subscribeOn(scheduler);
  }
  ...
  return observable;
}複製代碼

adapter最終建立了Observable,主咱們這裏分析其中開頭的兩步來:

  • 分異步和同步請求建立responseObservable
  • 根據返回的類型建立observable

這裏以異步爲例,看看CallEnqueueObservable

final class CallEnqueueObservable<T> extends Observable<Response<T>> {
  private final Call<T> originalCall;

  CallEnqueueObservable(Call<T> originalCall) {
    this.originalCall = originalCall;
  }

  @Override protected void subscribeActual(Observer<? super Response<T>> observer) {
    // Since Call is a one-shot type, clone it for each new observer.
    Call<T> call = originalCall.clone();
    CallCallback<T> callback = new CallCallback<>(call, observer);
    observer.onSubscribe(callback);
    call.enqueue(callback);// 這裏執行了enqueue
  }

  private static final class CallCallback<T> implements Disposable, Callback<T> {
    private final Call<?> call;
    private final Observer<? super Response<T>> observer;
    boolean terminated = false;

    CallCallback(Call<?> call, Observer<? super Response<T>> observer) {
      this.call = call;
      this.observer = observer;
    }

    @Override public void onResponse(Call<T> call, Response<T> response) {
      if (call.isCanceled()) return;

      try {
        observer.onNext(response);

        if (!call.isCanceled()) {
          terminated = true;
          observer.onComplete();
        }
      } catch (Throwable t) {
        ...
      }
    }

    @Override public void onFailure(Call<T> call, Throwable t) {
      if (call.isCanceled()) return;

      try {
        observer.onError(t);
      } catch (Throwable inner) {
        Exceptions.throwIfFatal(inner);
        RxJavaPlugins.onError(new CompositeException(t, inner));
      }
    }

    ...
  }
}複製代碼

subscribeActual方法內,主要作了三件事情:

  • clone了原有的call,由於OkHttp.Call只能使用一次
  • 設置了onSubscribe,可用於解除訂閱
  • 執行了enqueue請求


再看看第二步,這裏以BodyObservable爲例子:

final class BodyObservable<T> extends Observable<T> {
  private final Observable<Response<T>> upstream;

  BodyObservable(Observable<Response<T>> upstream) {
    this.upstream = upstream;
  }

  @Override protected void subscribeActual(Observer<? super T> observer) {
    upstream.subscribe(new BodyObserver<T>(observer));
  }

  private static class BodyObserver<R> implements Observer<Response<R>> {
    private final Observer<? super R> observer;
    private boolean terminated;

    BodyObserver(Observer<? super R> observer) {
      this.observer = observer;
    }

    @Override public void onSubscribe(Disposable disposable) {
      observer.onSubscribe(disposable);
    }

    @Override public void onNext(Response<R> response) {
      if (response.isSuccessful()) {
        observer.onNext(response.body());
      } else {
        ...
          observer.onError(t);
        ...
      }
    }

    @Override public void onComplete() {
      if (!terminated) {
        observer.onComplete();
      }
    }

    @Override public void onError(Throwable throwable) {
      if (!terminated) {
        observer.onError(throwable);
      } 
      ...
    }
  }
}複製代碼

代碼中的subscribeActual方法在subscribe以後執行,天然responseObservable就訂閱了BodyObserver,因此上面CallEnqueueObservable中的CallCallback.onResponse內,調用observer.onNext也就是BodyObserver.onNext,最後剛開始的觀察着就收到了response.body()

Converter

如今回到khttpCall.enqueue方法中,在其中還有一句重要的代碼沒有看,那就是response = parseResponse(rawResponse);,咱們來看看這其中作了什麼。

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 th
  rawResponse = rawResponse.newBuilder()
      .body(new NoContentResponseBody(rawBody.contentType(), rawBody.conte
      .build();
  ...
  ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
  try {
    T body = serviceMethod.toResponse(catchingBody);// 解析body,好比Gson解析
    return Response.success(body, rawResponse);
  } catch (RuntimeException e) {
    // If the underlying source threw an exception, propagate that rather 
    // a runtime exception.
    catchingBody.throwIfCaught();
    throw e;
  }
}

### ServiceMethod
R toResponse(ResponseBody body) throws IOException {
  return responseConverter.convert(body);
}複製代碼

能夠看出parseResponse最終調用了Converter.convert方法。這裏以經常使用的GsonConverterFactory爲例。

# GsonConverterFactory
@Override
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
  TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
  return new GsonResponseBodyConverter<>(gson, adapter);
}

# GsonResponseBodyConverter
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
  private final Gson gson;
  private final TypeAdapter<T> adapter;
  GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
    this.gson = gson;
    this.adapter = adapter;
  }
  @Override public T convert(ResponseBody value) throws IOException {
    JsonReader jsonReader = gson.newJsonReader(value.charStream());
    try {
      return adapter.read(jsonReader);
    } finally {
      value.close();
    }
  }
}複製代碼

responseBodyConverter方法中用到的type參數就是以前我在CallAdapter中提到的responseType方法的返回值。生成adapter方法,用於convert方法使用。OkHttpCall在這以後的代碼就比較簡單了,經過回調將轉換後得響應數據發送出去便可。

總結

本文分析了Retrofit的執行流程,其實包含了Retrofit、ServiceMethod、OkHttpCall、CallAdapter、Converter等方面。Retrofit的代碼相對是比較少,也比較容易理解的,不過倒是很好的架構實例。

若是想看retrofit中其餘一些代碼的註釋,請點擊這裏,若是其中發現不合適的描述,歡迎指出

若是在閱讀過程當中,有任何疑問與問題,歡迎與我聯繫。

博客:www.idtkm.com

GitHub:github.com/Idtk

微博:weibo.com/Idtk

郵箱:IdtkMa@gmail.com


參考

Retrofit分析-漂亮的解耦套路

拆輪子系列:拆 Retrofit

Retrofit

相關文章
相關標籤/搜索