Writing is limited, the content is more messy, we can look at the source code with this article as a supplement, the text is hasty, behind the free will be rewritten, if the error, welcome to correct ‘

Introduction of Retrofit

A type-safe HTTP client for Android and Java.

This is the website description of Retrofit, that is, a type safe network client request, when we use it to run phase will not type related to error, it can can the error detected during compilation, so in terms of “type safety”, generally don’t talk of a language, a type of security, but a tool, A library is type-safe, and this set of tools can help you detect type-related errors before you even run them, which is called type-safe.

Retrofit and OkHttp

Retrofit is a further encapsulation of OkHttp, making it easier for developers to develop on the web, but narrowing of functionality is inevitable as well, so Retrofit is not as powerful as OkHttp, but it is easy to use.

Simple to use

  1. Introduction of depend on

    implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'
    Copy the code
  2. Statement of the APi

    public interface GitHubService {
        @GET("users/{user}/repos")
        Call<List<Repo>> listRepos(@Path("user") String user);
    }
    Copy the code

    Here, Github information is obtained to simulate the posture of obtaining data. Repo is both the mapping class obtained by the request. To obtain it, we need to obtain the structure of the return body of the request in advance. Let’s make a request to https://api.github.com/users/yangSpica27/repos, access to the json, you can write the corresponding class, Kotlin users use Kotlin Data Class Code to automatically generate our Repo, and Java users can use GsonFormat.

  3. Create request Client

    // More additional configurations are available
     Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();
    Copy the code
  4. Instantiate interface

    GitHubService service = retrofit.create(GitHubService.class);
    Copy the code
  5. For the request

    Call<List<Repo>> repos = service.listRepos("yangSpica27");
    Copy the code

    There are two types of request

    repos.enquene()/ / asynchronous
    Copy the code
    repos.execute()/ / synchronize
    Copy the code

    A CallBack object is passed in for request initiation

     repos.enqueue(new Callback<List<Repo>>() {
            // Callback if the request succeeds
            @Override
            public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
                // Request processing, output results
               response.body()//body is the return body we get, List
            
               reponse.body.get(0).getName()// Get my Name
            }
    
            // Callback when the request fails
            @Override
            public void onFailure(Call<List<Repo>> call, Throwable throwable) {}});Copy the code

The source code to understand

Let’s start with enqueue, which is just a method of the Call interface.

Repos is created one level up service.listrepos (), but service.listrepos () is still just an interface, and it’s written by hand;

One level up, retrofit.create() creates the Service, and finally, here we can see the actual implementation code.

@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
  public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            newClass<? >[] {service},new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable 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); } args = args ! =null ? args : emptyArgs;
                returnplatform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code

Create is also at the heart of Retrofit

validateServiceInterface(service); Validation service interface

Direct source code

 private void validateServiceInterface(Class
        service) {
     // The first part
    if(! service.isInterface()) {throw new IllegalArgumentException("API declarations must be interfaces.");
    }
    // Part 2Deque<Class<? >> check =new ArrayDeque<>(1);
    check.add(service);
    while(! check.isEmpty()) { Class<? > candidate = check.removeFirst();if(candidate.getTypeParameters().length ! =0) {
        StringBuilder message =
            new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
        if(candidate ! = service) { message.append(" which is an interface of ").append(service.getName());
        }
        throw new IllegalArgumentException(message.toString());
      }
      Collections.addAll(check, candidate.getInterfaces());
    }

    // Part 3
    if (validateEagerly) {
      Platform platform = Platform.get();
      for (Method method : service.getDeclaredMethods()) {
        if(! platform.isDefaultMethod(method) && ! Modifier.isStatic(method.getModifiers())) { loadServiceMethod(method); }}}}Copy the code

The first part of validateServiceInterface()

   if(! service.isInterface()) {throw new IllegalArgumentException("API declarations must be interfaces.");
        }
Copy the code

Service is the interface that we pass in, and this part is doing type validation in case we pass in wrong types like classes, abstract classes, etc.

The second part of validateServiceInterface()

Deque<Class<? >> check =new ArrayDeque<>(1);
check.add(service);
  while(! check.isEmpty()) { Class<? > candidate = check.removeFirst();if(candidate.getTypeParameters().length ! =0) {
        StringBuilder message =
            new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
        if(candidate ! = service) { message.append(" which is an interface of ").append(service.getName());
        }
        throw new IllegalArgumentException(message.toString());
      }
      Collections.addAll(check, candidate.getInterfaces());
    }

Copy the code

The Deque is a two-way queue that allows us to insert in both directions. This step adds our interface to the check queue. Check. removeFirst() removes the interface that we just inserted and does the intermediate processing. Collections.addall (check, candidate.getinterfaces ()) passes the parent interface of the Service to the check queue, indicating that if our Service interface inherits from another interface, it will also process it. Let’s take a look at the process.

candidate.getTypeParameters().length ! =0
Copy the code

GetTypeParameters gets what parameters? This is a generic parameter, such as SpicaObject

, where “T” is the parameter. This judgment means that an error is reported if there is a parameter in “<>”. In other words, this interface is not allowed to be generic, nor is the parent interface allowed to be generic.

The third part of validateServiceInterface()

   if (validateEagerly) {
      Platform platform = Platform.get();
      for (Method method : service.getDeclaredMethods()) {
        if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
          loadServiceMethod(method);
        }
      }
    }
Copy the code

“Validategbit/s” translates to aggressive validation. When we used Retrofit, we created service interfaces that had some initialization when it was called for the first time, These initialization processes occur only when we call them for the first time and are not actively initialized when we are not using them. To prevent these methods from triggering problems, we can verify them during the CREATE process for easy testing. This is where “validategbit/s” aggressive validation comes in. LoadServiceMethod (method) is a method that is called to load, initialize, and check the interface type, not the default implementation, not the static method, which means Retrofit does not support static and default implementation interface methods.

create()

Coming back to the create section, let’s look at Retrofit’s core method proxy.newProxyInstance (), which is a dynamic Proxy.

Dynamic proxy:

  • Proxy :Retrofit creates a class that generates an object that implements a set of interfaces that we give it,

This class represents the methods in our interfaces.

  • Dynamic: this means that the class is generated as we run it, not compiled.

Let’s look at the arguments passed in:

  1. service.getClassLoader()

    A this

  2. new Class<? >[] {service}

    An array that contains our interfaces

  3. new InvocationHandler(Object proxy, Method method, @Nullable Object[] arg){}

    A listener. All calls to our interface implementation methods go through this Hanlder, such as service.listrepos () in the instance request, which means that our service is reflected by Retrofit into a ServiceProxy class. This class implements listRepos(). When we call listRepos(), InvocationHandler executes invoke(). This method takes three arguments: proxy, which is the proxy object, and Serviceproxy itself. Is not only the reflection of this method with Java is GitHubService. GetClass. GetDeclaredMethod (” listRepos “, String. Class), args is the parameters, such as case I making account.

 Proxy.newProxyInstance(
            service.getClassLoader(),
            newClass<? >[] {service},new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable 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); } args = args ! =null ? args : emptyArgs;
                returnplatform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }});Copy the code

If (method.getDeclaringClass() == object.class) specifies that methods in Object are excluded.

platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
Copy the code

Finally, the execution of this section is a judgment, determine the runtime environment, using different execution methods, such as Java 8 environment, open Jdk5 environment, etc.

loadServiceMethod(method).invoke(args)

  1. loadServiceMethod(method)
ServiceMethod<? > loadServiceMethod(Method method) { ServiceMethod<? > result = serviceMethodCache.get(method);if(result ! =null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method); serviceMethodCache.put(method, result); }}return result;
  }
Copy the code

This step is to find the method from the serviceMethodCache, return it if it does, and put it in the serviceMethodCache if it doesn’t. This is a caching process. Let’s take a look at how parseAnnotations() are created.

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}
Copy the code

This last, after a series of judgments to the HttpServiceMethod. ParseAnnotations (), in the step, see HttpServiceMethod. ParseAnnotations implementation, as follows:

 static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) {
    boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
    boolean continuationWantsResponse = false;
    boolean continuationBodyNullable = false;

    Annotation[] annotations = method.getAnnotations();
    Type adapterType;
    if (isKotlinSuspendFunction) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType =
          Utils.getParameterLowerBound(
              0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      } else {
        // TODO figure out if type is nullable or not
        // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
        // Find the entry for method
        // Determine if return type is nullable or not
      }

      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      adapterType = method.getGenericReturnType();
    }

 @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }
Copy the code

HttpServiceMethod inherits from ServiceMethod and is an implementation of it. First, a judgment isKotlinSuspendFunction is made to determine whether it belongs to the suspend method, and then a series of coroutine processing is carried out (this part is not very relevant to the basic function of Retrofit, so it will not be described in detail). GetGenericReturnType () is used to return the type of the method’s last return object, If the method String test(){return “test”} returns class java.lang.string, invoke() creates an okHttpCall object, This is the Call created when Call > repos = service.listrepos (“yangSpica27”), and adpt() converts the Call.

The call from repos.enqueue() gets the list of objects. The internal source code for this section is visible:

@Override
  public void enqueue(final Callback<T> callback) {
    Objects.requireNonNull(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) {
              throwIfFatal(e);
              callFailure(e);
              return;
            }

            try {
              callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t) {
              throwIfFatal(t);
              t.printStackTrace(); // TODO this is not great}}@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) {
              throwIfFatal(t);
              t.printStackTrace(); // TODO this is not great}}}); }Copy the code

As you can see, Retrofit first creates an Okhttp3 call using createRawCall() and then makes a request to get a sequence of bytecodes via Response = parseResponse(rawResponse); Returns after the bytecode is converted to the corresponding object.