Retrofit 2.x 的使用,優(yōu)勢,源碼分析和啟發(fā)

Retrofit是Square出品的Android Http請求框架,是基于Okhttp的(Okhttp也是該機(jī)構(gòu)搞的)。Retrofit經(jīng)歷了從1.x版本到2.x版本,是構(gòu)造REST風(fēng)格的HTTP客戶端的利器。

下面從 使用,優(yōu)勢,源碼分析和其代碼設(shè)計(jì)給我們帶來的啟發(fā)來學(xué)習(xí)Retrofit。

1.使用

首先看官方給的simple demo :

  public interface GitHub {
    @GET("/repos/{owner}/{repo}/contributors")
    Call<List<Contributor>> contributors(
        @Path("owner") String owner,
        @Path("repo") String repo);
  }

  public static void main(String... args) throws IOException {
    // Create a very simple REST adapter which points the GitHub API.
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(API_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

    // Create an instance of our GitHub API interface.
    GitHub github = retrofit.create(GitHub.class);

    // Create a call instance for looking up Retrofit contributors.
    Call<List<Contributor>> call = github.contributors("square", "retrofit");

    // Fetch and print a list of the contributors to the library.
    List<Contributor> contributors = call.execute().body();
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }

這是retrofit最簡單的用法,可以從代碼中簡單看出,使用方法如下:
(1).定義一個(gè)接口,接口中的方法用注解的方式聲明了Http 請求的相關(guān)參數(shù),包括使用get方法,相關(guān)參數(shù)等。方法的返回值為Call<List<Contributor>>,其中Contributor是定義的一個(gè)JavaBean類,即業(yè)務(wù)所需要的數(shù)據(jù)格式。
(2).實(shí)例化了一個(gè)Retrofit對象(用Retrofit的builder),指定了baseUrl(顧名思義),指定了ConverterFactory,即表示用Gson去解析返回值來得到JavaBean。
(3).用retrofit.create(GitHub.class)方法得到了GitHub實(shí)例對象(框架用動(dòng)態(tài)代理的方式幫我們生成了接口的實(shí)例,后續(xù)詳細(xì)說),調(diào)用對象方法得到call對象。其中,call對象有excute()和enqueue()方法,分別為同步和異步進(jìn)行網(wǎng)絡(luò)請求。

再看另一種retrofit的應(yīng)用——retrofit + rxjava2,這也是目前比較流行的一種網(wǎng)絡(luò)請求解決方案。當(dāng)然這個(gè)例子中也用到了一些其他相對高級的功能。

public interface INewsApi {
    @Headers(CACHE_CONTROL_NETWORK)
    @GET("nc/article/{type}/{id}/{startPage}-20.html")
    Observable<Map<String, List<NewsInfo>>> getNewsList(@Path("type") String type, @Path("id") String id,
                                                        @Path("startPage") int startPage);
        Cache cache = new Cache(new File(AndroidApplication.getContext().getCacheDir(), "HttpCache"),
                1024 * 1024 * 100);
        OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(cache)
                .retryOnConnectionFailure(true)
                .addInterceptor(sLoggingInterceptor)
                .addInterceptor(sRewriteCacheControlInterceptor)
                .addNetworkInterceptor(sRewriteCacheControlInterceptor)
                .connectTimeout(10, TimeUnit.SECONDS)
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(NEWS_HOST)
                .build();
        sNewsService = retrofit.create(INewsApi.class);
    public static Observable<NewsInfo> getNewsList(String newsId, int page) {
        String type;
        if (newsId.equals(HEAD_LINE_NEWS)) {wwet
            type = "headline";
        } else {
            type = "list";
        }
        return sNewsService.getNewsList(type, newsId, page * INCREASE_PAGE)
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .flatMap(_flatMapNews(newsId));
    }

可以對比該例子與前一個(gè)例子不同之處:
(1).接口中方法返回的是Observable對象,這就是Rx'Java中的那個(gè)Observable。
(2).定義了一個(gè)OkHttpClient對象并傳給了Retrofit的builder。OkHttpClient對象中設(shè)置了緩存,攔截器,超時(shí)時(shí)間等(此處就涉及到了OkHttp的一些知識(shí)),為框架進(jìn)行網(wǎng)絡(luò)請求做了一些約束。
(3).為Retrofit的builder增加了CallAdapterFactory為RxJavaCallAdapterFactory,可理解為將Call接口轉(zhuǎn)換成了RxJava的相應(yīng)接口。
(4).最后可見在調(diào)用時(shí)候用了RxJava的Observable類的相關(guān)方法,設(shè)置訂閱,觀察的線程等。

在構(gòu)造接口的方法時(shí),通過一些Annotation和參數(shù)指定HTTP請求的相關(guān)信息,包括請求方法(GET, POST, PUT, DELETE),url構(gòu)造,請求實(shí)體,F(xiàn)ORM ENCODED / MULTIPART實(shí)體,請求頭操作等。
詳情請見:http://square.github.io/retrofit/ 的API Declaration部分,幾乎涵蓋了所有HTTP請求的方法和參數(shù)。
2.優(yōu)勢
首先,Retrofit相對與以前的HTTP請求方式,如HttpURLConnection和OkHttpClient有很大優(yōu)勢。首先對于url構(gòu)造和請求實(shí)體構(gòu)造等過程,HttpURLConnection或OkHttpClient還需要手動(dòng)拼接url,還需要手動(dòng)對上傳或返回的數(shù)據(jù)流進(jìn)行操作。如果想進(jìn)行解析,還需要自己再去手動(dòng)引入一些解析框架如Gson等。此外,還需要用異步控制的方案或者框架如AyncTask/handler/Rxjava等,手動(dòng)再與網(wǎng)絡(luò)請求進(jìn)行耦合。如果設(shè)計(jì)不當(dāng),會(huì)導(dǎo)致這幾個(gè)框架之間耦合較大,導(dǎo)致如果有一天想替換掉某一部分(比如想用Rxjava替換Call或Gson替換Jackson)會(huì)很困難。
Retrofit在這幾個(gè)方面都比較有優(yōu)勢:首先在構(gòu)造HTTP請求時(shí),我們只需要去構(gòu)造接口的方法,框架會(huì)幫我們?nèi)?shí)現(xiàn)這些方法。按規(guī)則去構(gòu)造url,指定請求參數(shù)。可以直接用解析框架生成請求實(shí)體或解析結(jié)果。得到想要的異步請求的對象(Call/RxJava/RxJava2/guava/CompletableFuture)。請求構(gòu)造更方便,同時(shí)與解析框架和異步請求框架解耦(通過Retrofit.addxxxFactory指定用不同的框架),可以更便捷的替換不同的解析框架或者異步框架。
此外,性能上,由于Retrofit是基于OkHttp的,所以其繼承了OkHttp的優(yōu)秀性能。OkHttp使用Okio來大大簡化數(shù)據(jù)的訪問與存儲(chǔ),Okio是一個(gè)增強(qiáng) java.io 和 java.nio的庫。所以Retrofit性能和AsyncTask和Volley比還是很快。下面是網(wǎng)上找的性能對比圖,僅供參考,侵刪。

Paste_Image.png

3.源碼分析
下圖是摘自http://www.lxweimin.com/p/45cb536be2f4
可以作為參考,也并不完全認(rèn)同(如外觀模式本人認(rèn)為體現(xiàn)的并不充分)。 侵刪。

625299-29a632638d9f518f.png

(1).Retrofit
我們先從Retrofit類中的代碼開始分析。
先看大致了解Retrofit的成員變量

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

  final okhttp3.Call.Factory callFactory;
  final HttpUrl baseUrl;
  final List<Converter.Factory> converterFactories;
  final List<CallAdapter.Factory> adapterFactories;
  final Executor callbackExecutor;
  final boolean validateEagerly;
...

serviceMethodCache:可以看見每個(gè)Method對應(yīng)一個(gè)ServiceMethod,ServiceMethod類中包含了該方法的一些對應(yīng)信息,后續(xù)詳細(xì)分析。其他幾個(gè)參數(shù)都是retrofit在build時(shí)候可以傳入的參數(shù):
Retrofit.Builder.build():

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

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      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);
    }

callFactory:可見callFactory如果不指定則默認(rèn)new一個(gè)OkHttpClient,我們可以像上面例子一樣指定一個(gè)帶相關(guān)設(shè)置的OkHttpClient。
converterFactories/adapterFactories:從converterFactories和adapterFactories可以看出一個(gè)Retrofit對象可以對應(yīng)多個(gè)Converter.Factory和CallAdapter.Factory。
callbackExecutor:為回調(diào)函數(shù)所執(zhí)行的Executor,即添加加一個(gè)默認(rèn)的CallAdapterFactory,其回調(diào)在Executor上執(zhí)行。

adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

我們看一下Android平臺(tái)下defaultCallbackExecutor 實(shí)現(xiàn):

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 {
      private final Handler handler = new Handler(Looper.getMainLooper());

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

可見Android回調(diào)應(yīng)該默認(rèn)是在主線程上執(zhí)行的,但要注意到defaultCallbackExecutor 只對默認(rèn)的CallAdapterFactory生效,對于我們制定的比如Rxjava 的CallAdapter等是無效的。默認(rèn)下的ExecutorCallAdapterFactory如何實(shí)現(xiàn)我們接下來分析CallAdapter時(shí)候會(huì)詳細(xì)分析。
接下來,我們來看Retrofit的create方法。這個(gè)方法可以說是Retrofit框架中最精彩的一部分了:我們傳入接口的Class對象,返回了該接口的實(shí)例。我們可以想到Retrofit用了動(dòng)態(tài)代理實(shí)現(xiàn)了這個(gè)接口。

  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() {
          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.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            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);
          }
        });
  }

動(dòng)態(tài)代理方法Proxy.newProxyInstance返回便是實(shí)現(xiàn)該接口 的代理對象。invoke方法為接口方法具體的實(shí)現(xiàn)。我們重點(diǎn)關(guān)心這三行代碼:

            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);

首先對于每個(gè)方法對應(yīng)了一個(gè)ServiceMethod對象,loadServiceMethod方法先從map緩存中找該方法對應(yīng)的ServiceMethod對象,如果沒有則生成。根據(jù)ServiceMethod對象和參數(shù)得到了OkHttpCall對象。再由方法對應(yīng)的callAdapter將OkHttpCall適配成想要的異步回調(diào)接口。
下面兩個(gè)部分將介紹其中的ServiceMethod和OkHttpCall類。

(2).ServiceMethod
繼續(xù)看ServiceMethod類,保存了對應(yīng)方法的相關(guān)信息,其成員變量有:

final class ServiceMethod<R, T> {
...
  final okhttp3.Call.Factory callFactory;
  final CallAdapter<R, T> callAdapter;

  private final HttpUrl baseUrl;
  private final Converter<ResponseBody, R> responseConverter;
  private final String httpMethod;
  private final String relativeUrl;
  private final Headers headers;
  private final MediaType contentType;
  private final boolean hasBody;
  private final boolean isFormEncoded;
  private final boolean isMultipart;
  private final ParameterHandler<?>[] parameterHandlers;
...

callFactory,baseUrl,httpMethod,contentType,hasBody,isMultipart都是可以從Retrofit對象和方法的Annotation中獲取,為網(wǎng)絡(luò)請求中的一些參數(shù)。
我們重點(diǎn)關(guān)注其幾個(gè)成員變量:,responseConverter,callAdapter,parameterHandlers。
responseConverter和callAdapter的由Retrofit類中的converterFactories和adapterFactories中獲得。二者生成過程比較相似,所以我們以responseConverter為例進(jìn)行分析。
ServiceMethod.createResponseConverter():

    private Converter<ResponseBody, T> createResponseConverter() {
      Annotation[] annotations = method.getAnnotations();
      try {
        return retrofit.responseBodyConverter(responseType, annotations);
      } catch (RuntimeException e) { // Wide exception range because factories are user code.
        throw methodError(e, "Unable to create converter for %s", responseType);
      }
    }

Retrofit.responseBodyConverter():

  public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) {
    return nextResponseBodyConverter(null, type, annotations);
  }

  /**
   * Returns a {@link Converter} for {@link ResponseBody} to {@code type} from the available
   * {@linkplain #converterFactories() factories} except {@code skipPast}.
   *
   * @throws IllegalArgumentException if no converter available for {@code type}.
   */
  public <T> Converter<ResponseBody, T> nextResponseBodyConverter(Converter.Factory skipPast,
      Type type, Annotation[] annotations) {
    checkNotNull(type, "type == null");
    checkNotNull(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;
      }
    }

看見我們從Retrofit的converterFactories中進(jìn)行遍歷,根據(jù)該方法的returnType和annotations,converterFactory.responseBodyConverter()方法如果返回不是空則證明該convertFactory能夠生產(chǎn)解析該returnType和annotation產(chǎn)品,該方法便有這個(gè)Converter解析返回結(jié)果。
parameterHandlers對應(yīng)方法的各個(gè)參數(shù),其方法void apply(RequestBuilder builder, T value)用來拼接網(wǎng)絡(luò)請求的對象。

(3).OkHttpCall
在我們重點(diǎn)關(guān)注Retrofit的三行代碼中,OkHttpCall被生成并傳給該方法的callAdapter去適配成想要的接口。那么我們來看OkHttpCall類是干嘛的。OkHttpCall<T> implements Call<T>,其實(shí)現(xiàn)了Call的接口,之前提到了Call接口重要的方法是同步進(jìn)行網(wǎng)絡(luò)請求的execute異步的equeue方法。我們以excute方法為例進(jìn)行分析:

  @Override public Response<T> execute() throws IOException {
    okhttp3.Call call;

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

      if (creationFailure != null) {
        if (creationFailure instanceof IOException) {
          throw (IOException) creationFailure;
        } else {
          throw (RuntimeException) creationFailure;
        }
      }

      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException e) {
          creationFailure = e;
          throw e;
        }
      }
    }

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

    return parseResponse(call.execute());
  }

可見,主要的工作是用createRawCall()方法生成了okhttp3.Call對象,這個(gè)Call便是OkHttp3框架中的,而非我們之前談?wù)摰腞etrofit.Call。而后用okhttp3.Call對象的execute進(jìn)行網(wǎng)絡(luò)請求,并用parseResponce()解析。我們分別看createRawCall()和parseResponce()方法。

createRawCall():

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

該方法調(diào)用了serviceMethod.toRequest()方法:

  /** Builds an HTTP request from method arguments. */
  Request toRequest(Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    @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;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }

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

    return requestBuilder.build();
  }

serviceMethod.toRequest()調(diào)用各個(gè)參數(shù)handlers[p].apply(),該方法前文已提到,用來拼接網(wǎng)絡(luò)請求。
再看parseResponce():

  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);
    }

    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      T body = serviceMethod.toResponse(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;
    }
  }

parseResponse()根據(jù)okhttp3.Response進(jìn)行解析得到Retrofit.Response。首先分析了okhttp3.Response的HTTP狀態(tài)碼,如果是在200和300之間切不為204(No Content),205(Reset Content),則用 serviceMethod.toResponse()去解析Response的實(shí)體。
接著看 serviceMethod.toResponse():

  /** Builds a method return value from an HTTP response body. */
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }

可見serviceMethod.toResponse()用其對應(yīng)的responseConverter去解析返回實(shí)體得到想要的JavaBean對象。

(4).CallAdapter
到了(1)中Retrofit代碼中的最后一行了:

            return serviceMethod.callAdapter.adapt(okHttpCall);

所以我們來看一下CallAdapter相關(guān)的類。CallAdapter用來將Retrofit.Call接口轉(zhuǎn)換成我們想要的異步回調(diào)類型接口,而CallAdapter類
由CallAdapter.Factory生產(chǎn),這是一個(gè)典型的工廠模式。抽象類CallAdapter.Factory的get方法:

    /**
     * Returns a call adapter for interface methods that return {@code returnType}, or null if it
     * cannot be handled by this factory.
     */
    public abstract CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit);

也就是如果該工廠類能夠生產(chǎn)解析該Type和annotations的Adapter則生產(chǎn)一個(gè),否則返回空。我們看一個(gè)具體實(shí)現(xiàn):ExecutorCallAdapterFactory,他是默認(rèn)加給Retrofit的CallAdapterFactory:Retrofit在build時(shí)調(diào)用了adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor))而Android平臺(tái)Platform的defaultCallAdapterFactory就返回了ExecutorCallAdapterFactory對象。

final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
  final Executor callbackExecutor;

  ExecutorCallAdapterFactory(Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  @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) {
        return new ExecutorCallbackCall<>(callbackExecutor, call);
      }
    };
  }

  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) {
      if (callback == null) throw new NullPointerException("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);
            }
          });
        }
      });
    }

    @Override public boolean isExecuted() {
      return delegate.isExecuted();
    }

    @Override public Response<T> execute() throws IOException {
      return delegate.execute();
    }

    @Override public void cancel() {
      delegate.cancel();
    }

    @Override public boolean isCanceled() {
      return delegate.isCanceled();
    }

    @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
    @Override public Call<T> clone() {
      return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
    }

    @Override public Request request() {
      return delegate.request();
    }
  }
}

可見ExecutorCallAdapterFactory的get方法:如果returnType是Call則返回一個(gè)CallAdapter對象,否則返回空。ExecutorCallAdapterFactory是將Call適配成Call接口。但適配前和適配后的Call還是不一樣的:從enqueue方法中可以看到在callbackExecutor執(zhí)行了回調(diào)。callbackExecutor前文已介紹,在Android平臺(tái)上就是UI線程。

(5).ConverterFactory
Converter用來解析返回結(jié)果成JavaBean和將上傳的JavaBean序列化進(jìn)行上傳。以GsonConverterFactory為例:

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

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
      Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonRequestBodyConverter<>(gson, adapter);
  }
}

GsonResponseBodyConverter和GsonRequestBodyConverter就是用Gson去進(jìn)行解析。
如果我們返回的數(shù)據(jù)不是標(biāo)準(zhǔn)的Gson或其他格式,我們可以寫自定義的ConverterFactory。比如網(wǎng)絡(luò)請求返回Html,我們可以用Jsoup去解析Html提取想要的數(shù)據(jù)解析成Java對象,如下,是本人之前項(xiàng)目中寫過的一個(gè)用Jsoup解析網(wǎng)頁,把文章轉(zhuǎn)換成相應(yīng)數(shù)據(jù)對象的例子:

public class JsoupResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    ...
    @Override
    public T convert(ResponseBody value) throws IOException {
        try {
             Class classType = null;

            classType = Class.forName("com.example.mi.rockerfm.JsonBeans.ArticleContent");
            mObj = classType.newInstance();

            if (elementClass == ArticleContent.class) {

                mArticlesContent = (ArticleContent) mObj;
                Document document = Jsoup.parse(value.string());
                Element element = document.select("div.entry-content").select(".noselect").select(".entry-topic").first();
                Elements elementsImg = element.select("img");
                if(elementsImg != null && elementsImg.size()>0) {
                    for (int i = 0; i < elementsImg.size(); i++) {
                        Element e = elementsImg.get(i);
                        e.attr(SRC, e.attr(PIC_ORG));
                    }
                }
                Elements elementsSong = element.select("iframe");
                Call<SongDetial> call = null;
                if (elementsSong != null && elementsSong.size() > 0) {
                    mArticlesContent.setSongsMap(new HashMap<String, SongDetial.Song>((int) Math.ceil(elementsSong.size() / 0.75)));
                    for (int i = elementsSong.size() - 1; i >= 0; i--) {
                        Element e = elementsSong.get(i);
                        String src = e.attr(SRC);
                        if (TextUtils.isEmpty(src) || !src.contains(MUSIC_URL))
                            continue;
                        Matcher m = Pattern.compile("(?<=id=)(\\d+)").matcher(src);
                        String id = null;
                        while (m.find()) {
                            id = m.group();
                            break;
                        }
                        call = Net.getSongsApi().songDitials(id, "[" + id + "]");
                        call.enqueue(new LoadSongsDitialCallBack());
                        e.before(MUSIC_HTML_STRING);
                        e.parent().getElementsByClass("info").first().attr("id", id);
                        e.remove();
                    }
                }
                mArticlesContent.setContentHtml(getHtmlWithHead(element.html()));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) mObj;
    }
    ...
}

4.設(shè)計(jì)上的啟發(fā)
Retrofit能從眾多框架中脫穎而出,其優(yōu)勢主要有:1.Retrofit比較好的把幾個(gè)框架的功能組合起來,并沒有重復(fù)自造輪子,而是高效的把輪子進(jìn)行組合。其利用OkHTTP進(jìn)行網(wǎng)絡(luò)請求。并且Retrofit與異步請求框架和類解析框架解耦,使得Retrofit可以適配多種框架,使用者可以輕松的選擇適合自己項(xiàng)目的異步請求和解析的框架。2.Retrofit的面向接口的設(shè)計(jì)方式也是其主要優(yōu)勢,用戶通過編寫接口,框架替用戶實(shí)現(xiàn),用戶與框架的依賴只限于接口,網(wǎng)絡(luò)請求的相關(guān)參數(shù)等也更清晰。
下面,我們分析Retrofit中用到的一些設(shè)計(jì)模式和Java相關(guān)技術(shù)來分析Retrofit如何優(yōu)雅的使各個(gè)框架進(jìn)解耦并對外暴露的:

(1)Builder模式:
Retrofit在生成Retrofit對象和ServiceMethod對象時(shí)候都用到了Builder模式。
通過Builder來生成類的實(shí)例對象更加優(yōu)雅,尤其在一下情況下:
如果類有多個(gè)可選的構(gòu)造參數(shù)時(shí):參數(shù)較多,初始化時(shí)我們可以指定其中的一些而其他的參數(shù)如果不指定可以為默認(rèn)。
Builder可以模擬具名可選參數(shù)(類似Python等語言),如果參數(shù)太多,調(diào)用構(gòu)造參數(shù)時(shí)分清各個(gè)參數(shù)的順序會(huì)非常困難,Builder的優(yōu)勢便可以顯現(xiàn)出來。
當(dāng)然通過setter也可以來設(shè)置對象的各個(gè)參數(shù),但如果我們想類在生成之后就不允許再改變其參數(shù),對外暴露setter方法就并不合適了,用Builder來生對象可以保護(hù)對象參數(shù)被再次修改。
Builder也有缺點(diǎn):對多生成Builder對象,增加開銷,但整理來說在一些場景下還是利大于弊。

(2)工廠模式
Retrofit的Converter和Adapter都是由抽象工廠模式來生成的。抽象工廠隔離了具體類的生成,系統(tǒng)與產(chǎn)品的創(chuàng)建/組合/表示的過程相獨(dú)立:Retrofit的ConverterFactory和AdapterFactory都是在Retrofit對象生成時(shí)候制定的,而Converter和Adapter都是在Retrofit代理各個(gè)方法時(shí)候生成的。
同一個(gè)產(chǎn)品族的多個(gè)產(chǎn)品將在一起工作,所有的產(chǎn)品以同樣的接口出現(xiàn),多個(gè)ConverterFactory/AdapterFactory會(huì)根據(jù)傳入的類型和參數(shù),判斷是否能處理該類型/參數(shù),如果可以就生成相應(yīng)產(chǎn)品供使用。抽象工廠便于加新的具體工廠和產(chǎn)品族,對于代碼的拓展,無須修改已有系統(tǒng),只需要指定新的工廠類型,符合“開閉原則“,便于拓展。

(3)代理模式:
代理模式用代理類/對象來代替原始的類/對象,可以在原方法執(zhí)行之前和之后做一些操作(Log,做事務(wù)控制等),也可以用來實(shí)現(xiàn)延遲加載等,如一些Android的動(dòng)態(tài)加載框架都設(shè)計(jì)到了動(dòng)態(tài)代理模式。此外代理模式還能夠隱藏原始類的實(shí)現(xiàn),調(diào)用者只需要和代理類進(jìn)行交互即可。
Retrofit使用了動(dòng)態(tài)代理,用戶編寫接口,告訴Retrofit想要什么樣的方法,Retrofit通過動(dòng)態(tài)代理來生成實(shí)例對象。用動(dòng)態(tài)代理,完成了從接口到實(shí)例對象的過程。與靜態(tài)代理相比,動(dòng)態(tài)代理一套代碼可以同時(shí)代理多個(gè)原始類/接口。

(4)適配器模式:
適配器模式用來將接口A轉(zhuǎn)化成接口B,在Retrofit中用來將Call異步接口轉(zhuǎn)化成其他的異步接口。適配器模式使得程序更有拓展性,可以去適配其他框架的接口,如果程序需要引入新的框架,我們只需再添加一個(gè)新的適配器,就可以將原來的接口適配成新的接口。

(5)注解 Annotation:
Java中的Annotation可以用來修飾類、方法、變量、參數(shù)、包,可以視為是對方法/類等的參數(shù)的拓展,另外也可以用來動(dòng)態(tài)生成代碼/編譯器校驗(yàn)/運(yùn)行時(shí)提供一些參數(shù)等作用,框架可以通過一些Annotation對外暴露給調(diào)用者。Annotation分三種:SOURCE,CLASS,RUNTIME,分別在源碼階段/Class文件階段/運(yùn)行階段起作用。由于Retrofit中的注解需要在運(yùn)行時(shí)使用,所以都是RUNTIME類型的。許多目前熱門的Android框架都廣泛使用了Annotation來對調(diào)用者暴露接口,如Retrofit/greenDao/ButterKnife/EventBus…..其中,GreenDao3.x/ButterKnife都是用Annotation來動(dòng)態(tài)生成一部分代碼,不同的是GreenDao3.x的Annotation是SOURCE類型的,因?yàn)镚reenDao3.x是通過Gradle Plugin生成的代碼,發(fā)生在Gradle預(yù)編譯的過程中,而ButterKnife的Annotation是CLASS類型的,在編譯階段生成相應(yīng)代碼。Retrofit和EventBus的Annotation都是RUNTIME的,需要在運(yùn)行時(shí)用到。

5.總結(jié)
Retrofit的設(shè)計(jì)符合了高內(nèi)聚,低耦合的原則,有效的將其他框架組織起來,并使其之間解耦,這增強(qiáng)了Retrofit的易用性和靈活性。Retrofit合理運(yùn)用多種設(shè)計(jì)模式以及其面向接口的編程方式是其達(dá)到高內(nèi)聚低耦合的關(guān)鍵。沒有重新造輪子,而是復(fù)用其他輪子,讓輪子們高效組合到一起也是Retrofit的意義。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內(nèi)容