Retrofit 2.x 的使用,优势,源码分析和启发

Retrofit是Square出品的Android Http请求框架,是基于Okhttp的(Okhttp也是该机构搞的)。Retrofit经历了从1.x版本到2.x版本,是构造REST风格的HTTP客户端的利器。

下面从 使用,优势,源码分析和其代码设计给我们带来的启发来学习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).定义一个接口,接口中的方法用注解的方式声明了Http 请求的相关参数,包括使用get方法,相关参数等。方法的返回值为Call<List<Contributor>>,其中Contributor是定义的一个JavaBean类,即业务所需要的数据格式。
(2).实例化了一个Retrofit对象(用Retrofit的builder),指定了baseUrl(顾名思义),指定了ConverterFactory,即表示用Gson去解析返回值来得到JavaBean。
(3).用retrofit.create(GitHub.class)方法得到了GitHub实例对象(框架用动态代理的方式帮我们生成了接口的实例,后续详细说),调用对象方法得到call对象。其中,call对象有excute()和enqueue()方法,分别为同步和异步进行网络请求。

再看另一种retrofit的应用——retrofit + rxjava2,这也是目前比较流行的一种网络请求解决方案。当然这个例子中也用到了一些其他相对高级的功能。

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

可以对比该例子与前一个例子不同之处:
(1).接口中方法返回的是Observable对象,这就是Rx'Java中的那个Observable。
(2).定义了一个OkHttpClient对象并传给了Retrofit的builder。OkHttpClient对象中设置了缓存,拦截器,超时时间等(此处就涉及到了OkHttp的一些知识),为框架进行网络请求做了一些约束。
(3).为Retrofit的builder增加了CallAdapterFactory为RxJavaCallAdapterFactory,可理解为将Call接口转换成了RxJava的相应接口。
(4).最后可见在调用时候用了RxJava的Observable类的相关方法,设置订阅,观察的线程等。

在构造接口的方法时,通过一些Annotation和参数指定HTTP请求的相关信息,包括请求方法(GET, POST, PUT, DELETE),url构造,请求实体,FORM ENCODED / MULTIPART实体,请求头操作等。
详情请见:http://square.github.io/retrofit/ 的API Declaration部分,几乎涵盖了所有HTTP请求的方法和参数。
2.优势
首先,Retrofit相对与以前的HTTP请求方式,如HttpURLConnection和OkHttpClient有很大优势。首先对于url构造和请求实体构造等过程,HttpURLConnection或OkHttpClient还需要手动拼接url,还需要手动对上传或返回的数据流进行操作。如果想进行解析,还需要自己再去手动引入一些解析框架如Gson等。此外,还需要用异步控制的方案或者框架如AyncTask/handler/Rxjava等,手动再与网络请求进行耦合。如果设计不当,会导致这几个框架之间耦合较大,导致如果有一天想替换掉某一部分(比如想用Rxjava替换Call或Gson替换Jackson)会很困难。
Retrofit在这几个方面都比较有优势:首先在构造HTTP请求时,我们只需要去构造接口的方法,框架会帮我们去实现这些方法。按规则去构造url,指定请求参数。可以直接用解析框架生成请求实体或解析结果。得到想要的异步请求的对象(Call/RxJava/RxJava2/guava/CompletableFuture)。请求构造更方便,同时与解析框架和异步请求框架解耦(通过Retrofit.addxxxFactory指定用不同的框架),可以更便捷的替换不同的解析框架或者异步框架。
此外,性能上,由于Retrofit是基于OkHttp的,所以其继承了OkHttp的优秀性能。OkHttp使用Okio来大大简化数据的访问与存储,Okio是一个增强 java.io 和 java.nio的库。所以Retrofit性能和AsyncTask和Volley比还是很快。下面是网上找的性能对比图,仅供参考,侵删。

Paste_Image.png

3.源码分析
下图是摘自//www.greatytc.com/p/45cb536be2f4
可以作为参考,也并不完全认同(如外观模式本人认为体现的并不充分)。 侵删。

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:可以看见每个Method对应一个ServiceMethod,ServiceMethod类中包含了该方法的一些对应信息,后续详细分析。其他几个参数都是retrofit在build时候可以传入的参数:
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如果不指定则默认new一个OkHttpClient,我们可以像上面例子一样指定一个带相关设置的OkHttpClient。
converterFactories/adapterFactories:从converterFactories和adapterFactories可以看出一个Retrofit对象可以对应多个Converter.Factory和CallAdapter.Factory。
callbackExecutor:为回调函数所执行的Executor,即添加加一个默认的CallAdapterFactory,其回调在Executor上执行。

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

我们看一下Android平台下defaultCallbackExecutor 实现:

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回调应该默认是在主线程上执行的,但要注意到defaultCallbackExecutor 只对默认的CallAdapterFactory生效,对于我们制定的比如Rxjava 的CallAdapter等是无效的。默认下的ExecutorCallAdapterFactory如何实现我们接下来分析CallAdapter时候会详细分析。
接下来,我们来看Retrofit的create方法。这个方法可以说是Retrofit框架中最精彩的一部分了:我们传入接口的Class对象,返回了该接口的实例。我们可以想到Retrofit用了动态代理实现了这个接口。

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

动态代理方法Proxy.newProxyInstance返回便是实现该接口 的代理对象。invoke方法为接口方法具体的实现。我们重点关心这三行代码:

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

首先对于每个方法对应了一个ServiceMethod对象,loadServiceMethod方法先从map缓存中找该方法对应的ServiceMethod对象,如果没有则生成。根据ServiceMethod对象和参数得到了OkHttpCall对象。再由方法对应的callAdapter将OkHttpCall适配成想要的异步回调接口。
下面两个部分将介绍其中的ServiceMethod和OkHttpCall类。

(2).ServiceMethod
继续看ServiceMethod类,保存了对应方法的相关信息,其成员变量有:

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中获取,为网络请求中的一些参数。
我们重点关注其几个成员变量:,responseConverter,callAdapter,parameterHandlers。
responseConverter和callAdapter的由Retrofit类中的converterFactories和adapterFactories中获得。二者生成过程比较相似,所以我们以responseConverter为例进行分析。
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中进行遍历,根据该方法的returnType和annotations,converterFactory.responseBodyConverter()方法如果返回不是空则证明该convertFactory能够生产解析该returnType和annotation产品,该方法便有这个Converter解析返回结果。
parameterHandlers对应方法的各个参数,其方法void apply(RequestBuilder builder, T value)用来拼接网络请求的对象。

(3).OkHttpCall
在我们重点关注Retrofit的三行代码中,OkHttpCall被生成并传给该方法的callAdapter去适配成想要的接口。那么我们来看OkHttpCall类是干嘛的。OkHttpCall<T> implements Call<T>,其实现了Call的接口,之前提到了Call接口重要的方法是同步进行网络请求的execute异步的equeue方法。我们以excute方法为例进行分析:

  @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对象,这个Call便是OkHttp3框架中的,而非我们之前谈论的Retrofit.Call。而后用okhttp3.Call对象的execute进行网络请求,并用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;
  }

该方法调用了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()调用各个参数handlers[p].apply(),该方法前文已提到,用来拼接网络请求。
再看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()根据okhttp3.Response进行解析得到Retrofit.Response。首先分析了okhttp3.Response的HTTP状态码,如果是在200和300之间切不为204(No Content),205(Reset Content),则用 serviceMethod.toResponse()去解析Response的实体。
接着看 serviceMethod.toResponse():

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

可见serviceMethod.toResponse()用其对应的responseConverter去解析返回实体得到想要的JavaBean对象。

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

            return serviceMethod.callAdapter.adapt(okHttpCall);

所以我们来看一下CallAdapter相关的类。CallAdapter用来将Retrofit.Call接口转换成我们想要的异步回调类型接口,而CallAdapter类
由CallAdapter.Factory生产,这是一个典型的工厂模式。抽象类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);

也就是如果该工厂类能够生产解析该Type和annotations的Adapter则生产一个,否则返回空。我们看一个具体实现:ExecutorCallAdapterFactory,他是默认加给Retrofit的CallAdapterFactory:Retrofit在build时调用了adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor))而Android平台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则返回一个CallAdapter对象,否则返回空。ExecutorCallAdapterFactory是将Call适配成Call接口。但适配前和适配后的Call还是不一样的:从enqueue方法中可以看到在callbackExecutor执行了回调。callbackExecutor前文已介绍,在Android平台上就是UI线程。

(5).ConverterFactory
Converter用来解析返回结果成JavaBean和将上传的JavaBean序列化进行上传。以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去进行解析。
如果我们返回的数据不是标准的Gson或其他格式,我们可以写自定义的ConverterFactory。比如网络请求返回Html,我们可以用Jsoup去解析Html提取想要的数据解析成Java对象,如下,是本人之前项目中写过的一个用Jsoup解析网页,把文章转换成相应数据对象的例子:

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.设计上的启发
Retrofit能从众多框架中脱颖而出,其优势主要有:1.Retrofit比较好的把几个框架的功能组合起来,并没有重复自造轮子,而是高效的把轮子进行组合。其利用OkHTTP进行网络请求。并且Retrofit与异步请求框架和类解析框架解耦,使得Retrofit可以适配多种框架,使用者可以轻松的选择适合自己项目的异步请求和解析的框架。2.Retrofit的面向接口的设计方式也是其主要优势,用户通过编写接口,框架替用户实现,用户与框架的依赖只限于接口,网络请求的相关参数等也更清晰。
下面,我们分析Retrofit中用到的一些设计模式和Java相关技术来分析Retrofit如何优雅的使各个框架进解耦并对外暴露的:

(1)Builder模式:
Retrofit在生成Retrofit对象和ServiceMethod对象时候都用到了Builder模式。
通过Builder来生成类的实例对象更加优雅,尤其在一下情况下:
如果类有多个可选的构造参数时:参数较多,初始化时我们可以指定其中的一些而其他的参数如果不指定可以为默认。
Builder可以模拟具名可选参数(类似Python等语言),如果参数太多,调用构造参数时分清各个参数的顺序会非常困难,Builder的优势便可以显现出来。
当然通过setter也可以来设置对象的各个参数,但如果我们想类在生成之后就不允许再改变其参数,对外暴露setter方法就并不合适了,用Builder来生对象可以保护对象参数被再次修改。
Builder也有缺点:对多生成Builder对象,增加开销,但整理来说在一些场景下还是利大于弊。

(2)工厂模式
Retrofit的Converter和Adapter都是由抽象工厂模式来生成的。抽象工厂隔离了具体类的生成,系统与产品的创建/组合/表示的过程相独立:Retrofit的ConverterFactory和AdapterFactory都是在Retrofit对象生成时候制定的,而Converter和Adapter都是在Retrofit代理各个方法时候生成的。
同一个产品族的多个产品将在一起工作,所有的产品以同样的接口出现,多个ConverterFactory/AdapterFactory会根据传入的类型和参数,判断是否能处理该类型/参数,如果可以就生成相应产品供使用。抽象工厂便于加新的具体工厂和产品族,对于代码的拓展,无须修改已有系统,只需要指定新的工厂类型,符合“开闭原则“,便于拓展。

(3)代理模式:
代理模式用代理类/对象来代替原始的类/对象,可以在原方法执行之前和之后做一些操作(Log,做事务控制等),也可以用来实现延迟加载等,如一些Android的动态加载框架都设计到了动态代理模式。此外代理模式还能够隐藏原始类的实现,调用者只需要和代理类进行交互即可。
Retrofit使用了动态代理,用户编写接口,告诉Retrofit想要什么样的方法,Retrofit通过动态代理来生成实例对象。用动态代理,完成了从接口到实例对象的过程。与静态代理相比,动态代理一套代码可以同时代理多个原始类/接口。

(4)适配器模式:
适配器模式用来将接口A转化成接口B,在Retrofit中用来将Call异步接口转化成其他的异步接口。适配器模式使得程序更有拓展性,可以去适配其他框架的接口,如果程序需要引入新的框架,我们只需再添加一个新的适配器,就可以将原来的接口适配成新的接口。

(5)注解 Annotation:
Java中的Annotation可以用来修饰类、方法、变量、参数、包,可以视为是对方法/类等的参数的拓展,另外也可以用来动态生成代码/编译器校验/运行时提供一些参数等作用,框架可以通过一些Annotation对外暴露给调用者。Annotation分三种:SOURCE,CLASS,RUNTIME,分别在源码阶段/Class文件阶段/运行阶段起作用。由于Retrofit中的注解需要在运行时使用,所以都是RUNTIME类型的。许多目前热门的Android框架都广泛使用了Annotation来对调用者暴露接口,如Retrofit/greenDao/ButterKnife/EventBus…..其中,GreenDao3.x/ButterKnife都是用Annotation来动态生成一部分代码,不同的是GreenDao3.x的Annotation是SOURCE类型的,因为GreenDao3.x是通过Gradle Plugin生成的代码,发生在Gradle预编译的过程中,而ButterKnife的Annotation是CLASS类型的,在编译阶段生成相应代码。Retrofit和EventBus的Annotation都是RUNTIME的,需要在运行时用到。

5.总结
Retrofit的设计符合了高内聚,低耦合的原则,有效的将其他框架组织起来,并使其之间解耦,这增强了Retrofit的易用性和灵活性。Retrofit合理运用多种设计模式以及其面向接口的编程方式是其达到高内聚低耦合的关键。没有重新造轮子,而是复用其他轮子,让轮子们高效组合到一起也是Retrofit的意义。

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

推荐阅读更多精彩内容