Retrofit原理分析

一、Retrofit简介

Retrofit是现下Android端开发非常流行的一款网络请求框架,它通过动态代理的方式将Java接口翻译成网络请求,通过OkHttp发送请求,并且其具备强大的可扩展性,支持各种数据格式的转换以及RxJava。说到这里,我们来分析一下网络请求框架的本质,网络请求框架是一套提供给开发者使用的用于网络请求的API接口,我们知道,Android网络请求一般是基于Http协议的,而Http协议属于应用层的协议,具体的数据传输需要依赖传输层的TCP协议,Android系统提供了Socket编程接口给开发者建立TCP请求,所以具体数据的发送需要依赖Socket;Http协议属于应用层的协议,它是用来规定数据的传输格式的,用于传输双方都能按照固定的格式解读数据;所以,一个网络请求框架至少要包含以下几个功能:

1、提供接口给开发者传入请求参数;

2、编写Socket代码,建立TCP;

3、通过TCP连接,严格按照Http协议的格式将请求参数发送给服务端;

4、严格按照Http协议的格式解读服务端返回的数据;

5、提供相应的接口给开发者获得返回数据(一般是通过回调处理的)。

上面五点是一个网络请求框架必须具备的功能,当然,一个好的网络请求框架还应该具备如下特点:

1、提供给开发者使用的API尽可能简单;

2、添加了对网络缓存的处理,避免不必要的请求;

3、具有较高的性能;

4、具有较高的可扩展性。

我们常用的OkHttp、HttpURLConnection等网络请求框架,其内部就会将开发者传入的请求参数按照Http协议的格式组织好,并通过TCP连接发送给服务端,在收到服务端返回数据后,又会按照Http协议去解读数据,并提供相应的API(一般是回调)给开发者获得数据。由于OkHttp属于比较底层的网络请求框架,开发者在使用时还是会比较复杂,于是Retrofit对OkHttp进行了再度的封装,使得开发者在使用时更加的方便。

二、使用Retrofit请求网络数据的基本流程

1、在build中添加依赖

compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

2、定义请求接口类

import retrofit2.Call;

import retrofit2.http.Field;

import retrofit2.http.FormUrlEncoded;

import retrofit2.http.POST;

/**

* Created by dell on 2018/9/6.

*/

public interface TranslateApi {

    @POST("ajax.php?a=fy&f=auto&t=auto")

    @FormUrlEncoded

    Call translateRequest(@Field("w") String input);

}

3、创建Retrofit实例

Retrofit retrofit = new Retrofit.Builder() // 通过Builder模式构造一个Retrofit对象

                .baseUrl("http://fy.iciba.com/") // 配置HOST

                .addConverterFactory(GsonConverterFactory.create()) // 添加解读返回数据的对象,因为返回数据是Json格式的,所以这里采用GsonConverterFactory来解读

                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 添加适配OkHttpCall对象的CallAdapterFactory

                .build();

4、根据请求接口生成具体的请求实体对象

TranslateApi translateApi = retrofit.create(TranslateApi.class); // 传入定义的接口,获得一个实现了TranslateApi接口的实体对象

5、调用请求对象的请求方法生成能够发起网络请求的Call对象

Call call = translateApi.translateRequest(mInputEditText.getText().toString()); // 调用请求方法,返回一个Call对象

6、调用Call对象的enqueue方法发起异步网络请求

        call.enqueue(new Callback() { // 调用Call对象的enqueue方法发起异步网络请求,enqueue方法需要传入一个Callback对象,用来处理网络请求的回调

            @Override

            public void onResponse(Call call, Response response) { // 网络请求正确的回调

                TranslateBean translateBean = response.body(); // 处理返回结果

                if (translateBean.getContent().getOut() != null) {

                    mOutputText.setText(translateBean.getContent().getOut());

                } else {

                    mOutputText.setText(translateBean.getContent().getWordMeanString());

                }

            }

            @Override

            public void onFailure(Call call, Throwable t) { // 网络请求错误的回调

                mOutputText.setText("翻译出错了!");

            }

总结一下,首先我们定义了网络请求的接口,接口中配置了请求的基本信息,包括请求方法、请求参数等;接着我们生成了一个Retrofit对象,在这里可以配置请求的默认Host信息、添加解析返回数据格式的Factory、添加适配OkHttpCall的Factory等功能;然后调用retrofit对象的create方法,传入网络请求接口类,生成一个具体的网络请求实体对象;接下来我们调用了请求方法生成一个能够发起网络请求的Call对象,最后调用Call对象的enquene方法将发起异步的网络请求,并且在传入的Callback中处理网络请求的回调。

三、Retrofit原理分析

从上面的使用方法可以看出,Retrofit的核心是根据接口生成一个能够发起网络请求的对象,然后根据这个对象再发起网络请求

1、生成Retrofit对象

    public Retrofit build() {

      if (baseUrl == null) {

        throw new IllegalStateException("Base URL required.");

      }

      okhttp3.Call.Factory callFactory = this.callFactory;

      if (callFactory == null) {

        callFactory = new OkHttpClient(); // 如果没有设置网络请求框架,模式使用OkHttp处理网络请求

      }

      Executor callbackExecutor = this.callbackExecutor;

      if (callbackExecutor == null) {

        callbackExecutor = platform.defaultCallbackExecutor(); // 默认使用platform.defaultCallbackExecutor()处理返回结果

      }

      // Make a defensive copy of the adapters and add the default Call adapter.

      List adapterFactories = new ArrayList<>(this.adapterFactories);

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

      // Make a defensive copy of the converters.

      List converterFactories = new ArrayList<>(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,

          callbackExecutor, validateEagerly); // 根据配置信息生成一个Retrofit对象

    }

  }

Retrofit通过build模式来生成一个Retrofit对象,通过代码我们知道,Retrofit默认会使用OkHttp来发送网络请求,当然,我们也可以自己定制。

2、create方法根据接口生成具体的网络请求实体类

public T create(final Class 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, @Nullable 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 serviceMethod =

                (ServiceMethod) loadServiceMethod(method);  // 根据方法生成一个ServiceMethod对象(内部会将生成的ServiceMethod放入在缓存中,如果已经生成过则直接从缓存中获取)

            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); // 根据ServiceMethod对象和请求参数生成一个OkHttpCall对象,这个OkHttpCall能够调用OkHttp的接口发起网络请求

            return serviceMethod.callAdapter.adapt(okHttpCall); // 调用serviceMethod的callAdapter的adapt方法,并传入okHttpCall,返回一个对象,这个的目的主要是为了适配返回类型,其内部会对OkhttpCall对象进行包装

          }

        });

  }

Retrofit的create方法通过动态代理的模式,生成了实现了具体的网络请求接口的对象,并在InvocationHandler的invoke方法中统一处理网络请求接口实体对象的方法,invoke方法会通过方法构造一个ServiceMethod对象,并将其放入缓存中,然后根据ServiceMethod对象和网络请求的参数args去构造一个OkHttpCall对象,最后调用serviceMethod的callAdapter的adapt方法,传入将OkHttpCall对象,callAdapter的目的主要是为了适配OkHttpCall对象,其内部会对OkHttpCall对象进行包装,生成对应返回类型的对象。

动态代理的原理主要是在运行时动态生成代理类,然后根据代理类生成一个代理对象,在这个代理对象的方法中中又会调用InvocationHandler的invoke来转发对方法的处理,比如,TranslateApi生成的代码类代码大致应该如下:

public Class Translate implement Translate {

InvokeHandler mInvokeHandler;

Call translateRequest(String input) {

Method translateRequestMethod = Class.forName("packagename.TranslateApi").getMethod("translateRequest", String.class); // 通过反射获得TranslateApi的translateRequest方法

mInvokeHandler.invoke(this, translateRequestMethod, new Object[] {input}); // 调用InvokeHandler的invoke方法,并传入当前对象,方法,方法传入参数

}

}

代理类的代码是动态生成的,生成代码后我们就可以用ClassLoader将其加载到内存中,并通过反射生成代理对象,代理类会将方法的处理转发给InvokeHandler,所以所有对代理对象方法的调用都会由InvocationHandler的invoke方法处理。

3、loadServiceMethod方法

  ServiceMethod loadServiceMethod(Method method) { // 根据方法返回一个对应的ServiceMethod对象

    ServiceMethod result = serviceMethodCache.get(method); // 首先从缓存中获取

    if (result != null) return result;

    synchronized (serviceMethodCache) {

      result = serviceMethodCache.get(method);

      if (result == null) { // 如果缓存中没有则构造一个ServiceMethod对象并将其放入缓存中

        result = new ServiceMethod.Builder<>(this, method).build();

        serviceMethodCache.put(method, result);

      }

    }

    return result;

  }

loadServiceMethod首先会从缓存中获取ServiceMethod对象,如果没有,则通过Method和Retrofit对象构造一个ServiceMethod对象,并将其放入缓存中。

4、ServiceMethod的构造

ServiceMethod其实是用来存储一次网络请求的基本信息的,比如Host、URL、请求方法等,同时ServiceMethod还会存储用来适配OkHttpCall对象的CallAdpater。ServiceMethod的build方法会解读传入的Method,首先ServiceMethod会在CallAdpaterFactory列表中寻找合适的CallAdapter来包装OkHttpCall对象,这一步主要是根据Method的返回参数来匹配的,比如如果方法的返回参数是Call对象,那么ServiceMethod就会使用默认的CallAdpaterFactory来生成CallAdpater,而如果返回对象是RxJava的Obserable对象,则会使用RxJavaCallAdapterFactory提供的CallAdpater。然后build方法会解读Method的注解,来获得注解上配置的网络请求信息,比如请求方法、URL、Header等。

    public ServiceMethod build() {

      callAdapter = createCallAdapter(); // 查找能够适配返回类型的CallAdpater

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

      // 解读方法的注解

      for (Annotation annotation : methodAnnotations) {

        parseMethodAnnotation(annotation);

      }

      if (httpMethod == null) {

        throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");

      }

      if (!hasBody) {

        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;

      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.");

        }

        parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);

      }

      if (relativeUrl == null && !gotUrl) {

        throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);

      }

      if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {

        throw methodError("Non-body HTTP method cannot contain @Body.");

      }

      if (isFormEncoded && !gotField) {

        throw methodError("Form-encoded method must contain at least one @Field.");

      }

      if (isMultipart && !gotPart) {

        throw methodError("Multipart method must contain at least one @Part.");

      }

      return new ServiceMethod<>(this);

    }

我们来看一下查找CallAdpater的代码:

    private CallAdapter createCallAdapter() {

      Type returnType = method.getGenericReturnType(); // 获得方法的返回类型

      if (Utils.hasUnresolvableType(returnType)) {

        throw methodError(

            "Method return type must not include a type variable or wildcard: %s", returnType);

      }

      if (returnType == void.class) {

        throw methodError("Service methods cannot return void.");

      }

      Annotation[] annotations = method.getAnnotations();

      try {

        //noinspection unchecked

        return (CallAdapter) retrofit.callAdapter(returnType, annotations); // 调用retrofit的callAdpater方法查找合适的CallAdpater

      } catch (RuntimeException e) { // Wide exception range because factories are user code.

        throw methodError(e, "Unable to create call adapter for %s", returnType);

      }

    }

可以看到,会调用retrofit的callAdapter去查找合适的CallAdapter,传入的参数为方法的返回类型和注解

Retrofit的callAdapter方法:

  public CallAdapter nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,

      Annotation[] annotations) {

    checkNotNull(returnType, "returnType == null");

    checkNotNull(annotations, "annotations == null");

    int start = adapterFactories.indexOf(skipPast) + 1;

    for (int i = start, count = adapterFactories.size(); i < count; i++) { // 遍历所有的CallAdpaterFactory,找到能够适配的CallAdpater

      CallAdapter adapter = adapterFactories.get(i).get(returnType, annotations, this);

      if (adapter != null) {

        return adapter;

      }

    }

    ...

  }

Retrofit的CallAdpater方法会遍历所有的CallAdpaterFactory,找到能够适配的CallAdpater,我们首先来看一下默认的CallAdpaterFactory是如何生成CallAdapter的:

final class DefaultCallAdapterFactory extends CallAdapter.Factory {

  static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory();

  @Override

  public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) {

    if (getRawType(returnType) != Call.class) { // 首先判断方法的返回类型是不是Call类型,如果不是则说明这个CallAdpaterFactory适配不了

      return null;

    }

    final Type responseType = Utils.getCallResponseType(returnType);

    return new CallAdapter>() { // 返回CallAdpater

      @Override public Type responseType() {

        return responseType;

      }

      @Override public Call adapt(Call call) {

        return call; // 直接将call返回

      }

    };

  }

}

DefaultCallAdapterFactory是默认的CallAdpaterFactory,它在Retrofit构造时会加入到CallAdapterFactory列表中,可以看到,它只会去适配返回类型为Call的方法。

我们再来看一下返回类型为Observable是如何适配的:

首先,我们在构造Retrofit对象时需要加入适配RxJava返回对象的

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

我们来看一下RxJava2CallAdpaterFactory的get方法:

  @Override

  public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) {

    Class rawType = getRawType(returnType); // 获得返回类型

    if (rawType == Completable.class) { // 如果返回类型为RxJava的Completable类型,则可以适配

      // Completable is not parameterized (which is what the rest of this method deals with) so it

      // can only be created with a single configuration.

      return new RxJava2CallAdapter(Void.class, scheduler, isAsync, false, true, false, false,

          false, true);

    }

    boolean isFlowable = rawType == Flowable.class;

    boolean isSingle = rawType == Single.class;

    boolean isMaybe = rawType == Maybe.class;

    // 如果返回类型为RxJava的Flowable、Single、Maybe、Observable类型,则可以适配

    if (rawType != Observable.class && !isFlowable && !isSingle && !isMaybe) {

      return null;

    }

    boolean isResult = false;

    boolean isBody = false;

    Type responseType;

    if (!(returnType instanceof ParameterizedType)) {

      String name = isFlowable ? "Flowable"

          : isSingle ? "Single"

          : isMaybe ? "Maybe" : "Observable";

      throw new IllegalStateException(name + " return type must be parameterized"

          + " as " + name + " or " + name + "");

    }

    Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);

    Class rawObservableType = getRawType(observableType);

    if (rawObservableType == Response.class) {

      if (!(observableType instanceof ParameterizedType)) {

        throw new IllegalStateException("Response must be parameterized"

            + " as Response or Response");

      }

      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);

    } else if (rawObservableType == Result.class) {

      if (!(observableType instanceof ParameterizedType)) {

        throw new IllegalStateException("Result must be parameterized"

            + " as Result or Result");

      }

      responseType = getParameterUpperBound(0, (ParameterizedType) observableType);

      isResult = true;

    } else {

      responseType = observableType;

      isBody = true;

    }

    return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable,

        isSingle, isMaybe, false);

  }

可以看到,RxJava2CallAdpaterFactory能够处理返回类型为RxJava的Completable、Flowable、Single、Maybe、Observable类型,并提供一个RxJava2CallAdpater适配器。

5、根据ServiceMethod和args生成OkHttpCall对象

OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);

我们知道,ServiceMethod封装了网络请求的基本信息,比如Host、URL等,我们根据ServiceMethod和请求参数args就可以确定本次网络请求的所有信息了,OkHttpCall主要是将这些信息封装起来,并调用OkHttp的接口去发送网络请求,这里,我们就将OkHttpCall看成是一个处理网络请求的类即可。

6、serviceMethod.callAdpater.adpat(okHttpCall)

这一步主要是将能够处理网络请求的OkHttpCall对象通过适配器适配成方法返回类型的对象,以Observable为例,我们知道RxJava和Retrofit结合使用的代码大致如下:

Observable observable = requestApi.request("xiaoming");

observable.subscribleOn(Sechedulers.io())

        .observerOn(AndroidSecheduler.mainThread())

        .subscrible(new Observer() {

        public void onNext(DataBean dataBean) {

        }

        public void onError(Error e) {

        }

        ...

        })

也就是说我们通过调用接口返回了一个observable(被观察者)对象,然后给obserable绑定了一个Observer(观察者)对象,并且指定了处理的subscrible线程为子线程,处理observer回调的线程为主线程。

那么RxJava2CallAdapter的adapt方法是如何将一个Call对象适配成Observable对象的呢?

final class RxJava2CallAdapter implements CallAdapter {

  @Override public Object adapt(Call call) {

    Observable> responseObservable = isAsync

        ? new CallEnqueueObservable<>(call)

        : new CallExecuteObservable<>(call); // 生成自定义的Observable对象,并且其中封装了进行网络请求的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);

    }

    if (isFlowable) {

      return observable.toFlowable(BackpressureStrategy.LATEST);

    }

    if (isSingle) {

      return observable.singleOrError();

    }

    if (isMaybe) {

      return observable.singleElement();

    }

    if (isCompletable) {

      return observable.ignoreElements();

    }

    return observable;

  }

}

主要看第一行代码:

Observable> responseObservable = isAsync

        ? new CallEnqueueObservable<>(call)

        : new CallExecuteObservable<>(call); // 生成自定义的Observable对象,并且其中封装了进行网络请求的Call对象

这句代码会将能够进行网络请求的Call对象封装成一个自定义的Observable对象并返回,以CallExecuteObservable为例:

final class CallExecuteObservable extends Observable> {

  private final Call originalCall; // 能够进行网络请求的Call对象,也就是我们前面说的OkHttpCall对象

  CallExecuteObservable(Call originalCall) {

    this.originalCall = originalCall;

  }

  @Override protected void subscribeActual(Observer> observer) { // 这个方法会在Observable调用subscribe方法订阅观察者时调用

    // Since Call is a one-shot type, clone it for each new observer.

    Call call = originalCall.clone();

    observer.onSubscribe(new CallDisposable(call));

    boolean terminated = false;

    try {

      Response response = call.execute(); // 调用OkHttpCall对象,执行网络请求并获得响应结果

      if (!call.isCanceled()) {

        observer.onNext(response); // 调用Obserber的onNext方法,发送返回接口

      }

      if (!call.isCanceled()) {

        terminated = true;

        observer.onComplete();

      }

    } catch (Throwable t) {

      Exceptions.throwIfFatal(t);

      if (terminated) {

        RxJavaPlugins.onError(t);

      } else if (!call.isCanceled()) {

        try {

          observer.onError(t); // 请求出错则调用Observer的onError方法

        } catch (Throwable inner) {

          Exceptions.throwIfFatal(inner);

          RxJavaPlugins.onError(new CompositeException(t, inner));

        }

      }

    }

  }

  ...

}

可以看到CallExecuteObserable继承了Obserable,并重写了其subscribeActual方法,subscribeActual会在Obserable对象调用subcrible方法时调用,在subscribeActual方法中,首先是调用了OkHttpCall的execute()方法发起网络请求,并获得网络请求结果,如果请求成功,会调用Observer的onNext方法,并将请求结果传递给onNext方法,所以我们可以在Observer的onNext方法种处理网络请求成功的情况,而如果网络请求失败,则会调用Observer的onError方法。由上,我们知道,CallAdapter的作用是将OkHttpCall对象适配成方法的返回类型的对象。

至此,我们知道了Retrofit的原理,它内部通过动态代理生成接口的实体对象,然后通过解读注解来获得接口中定义的请求信息,通过CallAdapterFactory将OkHttpCall对象适配成接口中定义的返回类型,通过ConverFactory来解读数据,其底层真正处理网络请求的还是OkHttp框架(OkHttpCall通过调用OkHttp框架提供的Api处理网络请求)。

四、总结

Retrofit是一款能够将Java接口转换成一个能够进行网络请求对象的框架,具有使用简单,可扩展性强等优点,其内部通过动态代理模式生成接口的实体对象,并且在InvocationHandler中统一处理请求方法,通过解读方法的注解来获得接口中配置的网络请求信息,并将网络请求信息和请求参数一起封装成一个OkHttpCall对象,这个OkHttpCall对象内部通过OkHttp提供的Api来处理网络请求,为了将OkHttpCall对象适配成方法的返回类型,Retrofit提供了配置CallAdpaterFactory的Api,比如RxJava2CallAdapterFactory就会将OkHttpCall对象适配成一个Observable对象,并在Obserable的subscribleActual方法中调用OkHttpCall对象发起网络请求并回调Observser的onNext方法来处理网络请求返回的数据。Retrofit还提供了配置数据格式转换的API,可以针对不同的数据类型进行处理。

反观一下Retrofit,其内部的设计结构非常清晰,通过动态代理来处理接口,通过OkHttp来处理网络请求,通过CallAdapterFactory来适配OkHttpCall,通过ConverterFactory来处理数据格式的转换,这符合面对对象设计思想的单一职责原则,同时,Retrofit对CallAdpaterFactory和ConverterFactory的依赖都是依赖其接口的,这就让我们可以非常方便的扩展自己的CallAdpaterFactory和ConverterFactory,这符合依赖倒置原则;不管Retrofit内部的实现如何复杂,比如动态代理的实现、针对注解的处理以及寻找合适的适配器等,Retrofit对开发者隐藏了这些实现细节,只提供了简单的Api给开发者调用,开发者只需要关注通过的Api即可实现网络请求,这种对外隐藏具体的实现细节的思想符合迪米特原则。另外,Retrofit内部大量使用了设计模式,比如构造Retrofit对象时使用了Builder模式,处理接口时是用来动态代理模式,适配OkHttpCall时使用了Adapter模式,生成CallAdpater和Converter时使用了工厂模式。Retrofit的设计正是因为遵循了面向对象的思想,以及对设计模式的正确应用,才使得其具备结构清晰、易于扩展的特点。

————————————————

版权声明:本文为CSDN博主「xiao_nian」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/xiao_nian/article/details/87802483

你可能感兴趣的:(Retrofit原理分析)