preface

This article is to understand Retrofit’s working principle with three questions;

  • 1. What happens when we call the defined interface, and how is the interface API actually parsed?
  • 2. How Retrofit works with Okhttp because sending requests is ultimately Okhttp;
  • 3. How does corvert transform the returned data into an RxJava Observable?

With these three questions in mind, we begin the following presentation.

The use of the Retrofit

public interface MedalApi {
    String url = "employeeMedal/medalList.do";

    @FormUrlEncoded
    @POST(url)
    Observable<AchievedMedalResult> getAchievedMedal(@Field("account") String account,
                                                 @Field("accountType") String accountType,
                                                 @Field("queryEmployeeId263") String queryEmployeeId263);

    @GET(url)
    Observable<AchievedMedalResult> testMedal();

    @POST("/example_copy_copy/medallist")
    Observable<AchievedMedalDetailResult> getAchievedMedalDetail(@Field("account") String account,
                                                                 @Field("accountType") String accountType,
                                                                 @Field("queryEmployeeId263") String queryEmployeeId263,
                                                                 @Field("queryMedalCode") String queryMedalCode ); } public static <T> T createService(Class<T> service, HttpLoggingInterceptor loggingInterceptor = new Httploggingtor (new) HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) { L.d(TAG, message); }}); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient okHttpClient = new OkHttpClient.Builder() //.addInterceptor(new LoggerInterceptor("TAG")) .connectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) .readTimeout(READ_TIMEOUT, TimeUnit. MILLISECONDS) / * close OkHttp failure retry connection mechanism, which contribute to the Posting repeated question * /. RetryOnConnectionFailure (false) .addInterceptor(loggingInterceptor) .addInterceptor(new HttpHeadInterceptor()) .addInterceptor(new SecurityInterceptor(appInstance.instance)) // Encrypt and decrypt. AddNetworkInterceptor (new StethoInterceptor()).dns(new) SunlandDNS()) // other configurations.build (); Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build();return retrofit.create(service);
    }
Copy the code

Above is the actual use, do some simple configuration can send the request, the return data is converted into good entity class, really very convenient.

The principle of analytic

The above is a brief introduction to the use of the method, now to get into the main topic, talk about the principle behind.

Retrofit instantiation

First of all, how is it initialized? Where is the entry? Obviously, it’s the build builder mode, which is similar to okHTTP.

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

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

      // Add the built-in converter factory first. This prevents overriding its behavior but also
      // ensures correct behavior when using converters that consume all types.
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);

      returnnew Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories), unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly); }}Copy the code

It accepts parameters passed in, such as urls, initializes okHttpClient, covert, etc., and then directly new a Retrofit object. This saves the initialized values into the Retrofit object.

  Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
      @Nullable Executor callbackExecutor, boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
    this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }
Copy the code

These parameters are very important, either by default or passed in during external initialization.

Retrofit’s Create ()

Ok, so we have our Retrofit instance, so let’s go to the create method.

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return(T) Proxy.newProxyInstance(service.getClassLoader(), new Class<? >[] { service }, newInvocationHandler() {
          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<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            returnserviceMethod.adapt(okHttpCall); }}); }Copy the code

We pass the API as a class parameter, which uses Java dynamic proxy technology. We invoke the invoke method to execute the API methods

  • Check method (); object ();
  • Validation of the platform is done by default in java8, but not in android.
  • The focus is on the last three lines, creating ServiceMethod, instantiating OkHttpCall, and Servicemethod.adapt.

Next, we analyze the last three lines of code;

LoadServiceMethod:

private final Map<Method, ServiceMethod<? ,? >> serviceMethodCache = new ConcurrentHashMap<>(); ServiceMethod<? ,? > loadServiceMethod(Method method) { ServiceMethod<? ,? > result = serviceMethodCache.get(method);if(result ! = null)return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if(result == null) { result = new ServiceMethod.Builder<>(this, method).build(); serviceMethodCache.put(method, result); }}return result;
  }
Copy the code

The serviceMethodCache contains key/value pairs for Method and ServiceMethod. The ServiceMethod is first read from the map and used if the current value exists. Otherwise, create a ServiceMethod. This logic is very simple, is the use of caching mechanism, because Java dynamic proxy and annotation parsing is time-consuming, so caching is necessary. Let’s take a look at what ServiceMethod is, which requires a lot of effort to get or create. Build of ServiceMethod ()

      this.retrofit = retrofit;
      this.method = method;
      this.methodAnnotations = method.getAnnotations();
      this.parameterTypes = method.getGenericParameterTypes();
      this.parameterAnnotationsArray = method.getParameterAnnotations();
    }
Copy the code

So we’re going to pass in retrofit and Method as parameters, and then we’re going to get some of the parameters in method, and yes, the API looks pretty abstract, with annotations, parameter types, parameter annotations and so on, so we’re going to get those parameters in the API first, and then we’re going to look at build ();

    public ServiceMethod build() {
      callAdapter = createCallAdapter();
      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);
    }
Copy the code

This method seems long, but it is not difficult. Let’s explain it step by step:

  • 1. Create a CallAdapter. This method is eventually created in RetroFIT, for example by creating a Calladapter for RxJava
  • 2. Create responseConverter, a covert that returns the result, which was eventually created in RetroFIT, such as creating a GsonConveter
  • 3. Find all annotations in method and parse them one by one. HTTP requests post, GET, PUT and other annotations are parsed here.
    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);
      } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
        if(! Void.class.equals(responseType)) { throw methodError("HEAD method must use Void as response type."); }}Copy the code
  • Httpmethod = ‘post’; httpMethod = ‘get’; httpMethod = ‘post’;
  • 5. Perform additional checks on the request, such as post request must contain body, etc. And whether the URL is empty.
  • 6. After all the verification passes, the final instantiation is as follows:
 ServiceMethod(Builder<R, T> builder) {
    this.callFactory = builder.retrofit.callFactory();
    this.callAdapter = builder.callAdapter;
    this.baseUrl = builder.retrofit.baseUrl();
    this.responseConverter = builder.responseConverter;
    this.httpMethod = builder.httpMethod;
    this.relativeUrl = builder.relativeUrl;
    this.headers = builder.headers;
    this.contentType = builder.contentType;
    this.hasBody = builder.hasBody;
    this.isFormEncoded = builder.isFormEncoded;
    this.isMultipart = builder.isMultipart;
    this.parameterHandlers = builder.parameterHandlers;
  }
Copy the code

Ok, this is a bit too much, let’s go back to the invoke method of the proxy class.

OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
Copy the code

Here we instantiate the OkHttpCall, which takes the serviceMethod we just created. This call is essentially the okHTTP call, right, that ultimately calls the network request. If that’s all you need to do, then it’s time to send the request. I’ve decided to split this into two parts: adapt returns a normal Call, and an RxJava Observable, which is essentially two different uses of Retrofit.

return serviceMethod.adapt(okHttpCall);
Copy the code

The return type is a Call to Call

The return type of the adapt method is determined by the Servicemethod in okHttpCall. OkHttpCall implements Call, returns Call, and then executes the request. Requests can be executed synchronously or asynchronously. The synchronous request method is call.execute();

  @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 if (creationFailure instanceof RuntimeException) {
          throw (RuntimeException) creationFailure;
        } else {
          throw (Error) creationFailure;
        }
      }

      call = rawCall;
      if(call == null) { try { call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); // Do not assign a fatal error to creationFailure. creationFailure = e; throw e; }}}if (canceled) {
      call.cancel();
    }

    return parseResponse(call.execute());
  }
Copy the code

RawCall sends the request;

  private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = serviceMethod.toCall(args);
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }
Copy the code

To continue with

  /** Builds an HTTP request from method arguments. */
  okhttp3.Call toCall(@Nullable 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 callFactory.newCall(requestBuilder.build());
  }
Copy the code

This procedure calls servicemethod’s toCall method to construct the network request, returns okHttp3. call, then calls ececute () inside the call, and parseResponse to parse the returned result. I’m not going to do too much analysis here.

Moving on to asynchronous calls: In fact, once you know about synchronization, asynchrony is about as good as it gets:

  @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(); } catch (Throwable t) { throwIfFatal(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) {
        Response<T> response;
        try {
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return; } try { callback.onResponse(OkHttpCall.this, response); } catch (Throwable t) { t.printStackTrace(); } } @Override public void onFailure(okhttp3.Call call, IOException e) { callFailure(e); } private void callFailure(Throwable e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { t.printStackTrace(); }}}); }Copy the code

Put the newly created request into the thread pool and use callback to parse the result. That’s the normal call method that returns a call.

For calls in RxJava

How to initialize it when we use it:

     .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
     .addConverterFactory(GsonConverterFactory.create())
Copy the code

Here are the RxJava and GsonConvert used, saved in Retrofit objects for later use. CreateCallAdapter () is called by build () in ServiceMethod. The function in Retrofit is eventually called:

public CallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, TypereturnType,
      Annotation[] annotations) {
    checkNotNull(returnType, "returnType == null");
    checkNotNull(annotations, "annotations == null");

    int start = callAdapterFactories.indexOf(skipPast) + 1;
    for(int i = start, count = callAdapterFactories.size(); i < count; i++) { CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations, this);
      if(adapter ! = null) {returnadapter; }}... }Copy the code

It’s just iterating through the CallAdapter in the callAdapterFactories that we just added CallAdapterFactory to, yeah, So you end up calling the RxJavaCallAdapterFactory get method

@Override public CallAdapter<? ,? > get(TypereturnType, Annotation[] annotations, Retrofit retrofit) {
 ...
    if(! (returnType instanceof ParameterizedType)) {
      String name = isSingle ? "Single" : "Observable";
      throw new IllegalStateException(name + " return type must be parameterized"
          + " as " + name + "<Foo> or " + name + "<? extends Foo>");
    }

    Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType); Class<? > rawObservableType = getRawType(observableType); .return new RxJavaCallAdapter(responseType, scheduler, isAsync, isResult, isBody, isSingle,
        false);
  }
Copy the code

Here’s the thing: The get method, after some processing, ends up instantiating an RxJavaCallAdapter object and then calling the Adapt method in that class.

@Override public Object adapt(Call<R> call) { OnSubscribe<Response<R>> callFunc = isAsync ? new CallEnqueueOnSubscribe<>(call) : new CallExecuteOnSubscribe<>(call); OnSubscribe<? > func;if (isResult) {
      func = new ResultOnSubscribe<>(callFunc);
    } else if (isBody) {
      func = new BodyOnSubscribe<>(callFunc);
    } else{ func = callFunc; } Observable<? > observable = Observable.create(func);if(scheduler ! = null) { observable = observable.subscribeOn(scheduler); }if (isSingle) {
      return observable.toSingle();
    }
    if (isCompletable) {
      return observable.toCompletable();
    }
    return observable;
  }
Copy the code
  T adapt(Call<R> call) {
    return callAdapter.adapt(call);
  }

Copy the code

Therefore, different CallAdapters call the adapt in different classes and return different results. The default one is always a call, while RxJava returns an Observable. Then use Rxjava to send network requests:

  @Override public void call(Subscriber<? super Response<T>> subscriber) {
    // Since Call is a one-shot type.clone it for each new subscriber.
    Call<T> call = originalCall.clone();
    CallArbiter<T> arbiter = new CallArbiter<>(call, subscriber);
    subscriber.add(arbiter);
    subscriber.setProducer(arbiter);

    Response<T> response;
    try {
      response = call.execute();
    } catch (Throwable t) {
      Exceptions.throwIfFatal(t);
      arbiter.emitError(t);
      return; } arbiter.emitResponse(response); }}Copy the code

Call.execute () is the one that executes the web request, so it’s the same as the one that gets Respone and parses it. This explains how to use RxJava to send the web request. Moving on, the analysis method is similar to the one above, available in the Build of ServiceMethod

responseConverter = createResponseConverter();
Copy the code

This is to create an Convert, and this Convert is passed in when we initialize it, we’re passing a GsonConverterFactory, so like the Adapter, we’ll end up inside Retrofit:

{
    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 uncheckedreturn(Converter<ResponseBody, T>) converter; }}}Copy the code

The code is similar to the analysis adapter, and eventually leads to the GsonConverterFactory

public Converter<ResponseBody, ? > responseBodyConverter(Typetype, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);
  }
Copy the code

When Retrofit parses Respone data, it calls the following ServiceMothod method:


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

Continue to follow:

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

Gson processes the return result, converts it directly into an object, and encapsulates it as an Observable.

conclusion

After the analysis, we will take a look at the results of the three questions at the beginning of the article. Finally, we will look at the last picture, the analysis flow chart of Retrofit: