Retrofit principle:

To understand Retrofit, we first need to know about annotations and dynamic proxies

1. Annotations

Our most common annotation is the @Override annotation built into Java:

This annotation indicates that the method overrides the parent class. The compiler will issue an error message if the method is incorrectly signed.

This means that the @Override annotation takes effect at compile time. Hold Down CTRL and click on @Override:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Copy the code

@target is the annotation applied to the annotation, indicating the Target (a method or field) that the annotation is applied to.

@retention indicates the scope of the annotation:

Retentionpolicy. SOURCE: Annotations are only kept in the SOURCE file. When Java files are compiled into class files, annotations are discarded by the compiler. Retentionpolicy. CLASS: Annotations are kept in the CLASS file, but are discarded when the JVM loads the CLASS file, which is the default lifecycle. Retentionpolicy. RUNTIME: Annotations are not only saved to the class file, but also exist after the CLASS file is loaded by the JVM, so it can be read by reflection.

Life cycle length SOURCE < CLASS < RUNTIME, so the former can work where the latter must work.

Let’s look at an example of a write interface from Retrofit:

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

Anyone who has used Retrofit knows that this interface method is transformed into a method that invokes a network request.

Look @ the GET:

/** Make a GET request. */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  /**
   * A relative or absolute path, or full URL of the endpoint. This value is optional if the first
   * parameter of the method is annotated with {@link Url @Url}.
   *
   * <p>See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how
   * this is resolved against a base URL to create the full endpoint URL.
   */
  String value() default "";
}
Copy the code

Note This annotation is not only saved in the class file, but still exists after the CLASS file is loaded by the JVM.

2. Dynamic proxy

Static proxy:

public interface IProxy {
    void hello();
    void bye();
}
Copy the code
public class Greet implements IProxy{ @Override public void hello() { System.out.print("hello"); } @Override public void bye() { System.out.print("bye"); }}Copy the code
public class GreetToWorld implements IProxy{ public GreetToWorld(IProxy greet) { this.greet = greet; } IProxy greet; @Override public void hello() { greet.hello(); System.out.println(",world"); } @Override public void bye() { greet.bye(); System.out.println(",world"); }}Copy the code
public class Test { public static void main(String[] args) { Greet greet = new Greet(); GreetToWorld = new GreetToWorld(greet); greetToWorld.hello(); greetToWorld.bye(); }}Copy the code

Output:

The disadvantage of static proxies is that each proxyed class needs to create a new proxy class, even though the proxy class simply adds the same logic.

We need to generate A proxy object. Calling method A of this object is equivalent to calling method A of the proxied object and adding its own logic B. We need the ClassLoader for the proxy object, and the methods the proxy object needs to be propped (represented by interfaces), and then customize our own logic B.

Dynamic proxy:

public class Test { public static void main(String[] args) { Greet greet = new Greet(); / / dynamic Proxy hello IProxy dynamicProxy = (IProxy) Proxy. NewProxyInstance (Greet. Class. GetClassLoader (), new class <? >[]{IProxy.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object res = method.invoke(greet, args); A system.out. print("world! "); B return res; }}); dynamicProxy.hello(); dynamicProxy.bye(); }}Copy the code

Output:

Then look at Retrofit:

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

Retrofit converts a GitHub interface method into a network request by creating a new Instance of Retrofit and calling GitHub GitHub = retrofit.create(GitHub.class).

    // 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);
Copy the code

To make a network request, use:

    // 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();
Copy the code

Call is used to send network requests. Each Call generates a pair of its own

. You can use the Clone method to create a new Call with the same parameters and address to poll or retry. Execute () sends a synchronous request and enqueue () sends an asynchronous request.
,>

The Github. Polymorphism (” Square “, “retrofit”) certainly doesn’t call the Ficolin-3 method in the Github interface, which can’t be called. At this point, the ficolin-3 method in the protested-Github interface is called. All of the interfaces are dynamically proxy with the same template for network requests. The proxy process is implemented in GitHub GitHub = retrofit.create(GitHub.class) :

public <T> T create(final Class<T> service) { validateServiceInterface(service); return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class<? >[] {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; return platform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code

ValidateServiceInterface (Service) validates that the incoming Service is an interface, and returns a proxy object that changes all requests to loadServiceMethod(Method).invoke(args).

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

There’s a cache, not cache is invoked ServiceMethod. ParseAnnotations (this, method) analysis the Annotation of the interface:

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); } else if (annotation instanceof PATCH) { parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true); } else if (annotation instanceof POST) { parseHttpMethodAndPath("POST", ((POST) annotation).value(), true); } else if (annotation instanceof PUT) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true); } else if (annotation instanceof OPTIONS) { parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false); } else if (annotation instanceof HTTP) { HTTP http = (HTTP) annotation; parseHttpMethodAndPath(http.method(), http.path(), http.hasBody()); } else if (annotation instanceof retrofit2.http.Headers) { String[] headersToParse = ((retrofit2.http.Headers) annotation).value(); if (headersToParse.length == 0) { throw methodError(method, "@Headers annotation is empty."); } headers = parseHeaders(headersToParse); } else if (annotation instanceof Multipart) { if (isFormEncoded) { throw methodError(method, "Only one encoding annotation is allowed."); } isMultipart = true; } else if (annotation instanceof FormUrlEncoded) { if (isMultipart) { throw methodError(method, "Only one encoding annotation is allowed."); } isFormEncoded = true; }}Copy the code

Annotations are resolved here as requested methods.

Retrofit is only responsible for producing objects, producing work objects that can do network requests, it’s kind of like a factory that only provides products, the factory itself doesn’t process network requests, so the products can process network requests.Retrofit uses OkHttpClient to implement network requests. This OkHttpClient cannot be replaced by other network execution frameworks such as Volley, but Retrofit allows us to extend OkHttpClient by ourselves. The most commonly extended is the Interceptor Interceptor.

The extension is the automatic conversion of returned data types, converting one data object to another. In the above scenario, the GsonConverterFactory converts Http accessed JSON strings into Java data objects, BizEntity.

The extension is the automatic conversion of the network work object callWorker. The Call object that performs network requests in Retrofit is converted into the Call object defined in the interface, such as Rxjava Observable.

OKHTTP features:

  • Support for Http2.0, which allows all requests from the same host to share sockets.

    • Http2.0: Secure because Http2.0 is based on the HTTPS protocol, efficient because it transmits data through binary framing
  • Use connection pooling to reduce request latency if HTTP / 2 is not available.

  • Transparent GZIP reduces download size.

  • Response caching can avoid networks of repeated requests.

    • OKHttp provides a caching mechanism to cache responses to our HTTP and HTTPS requests into the file system
    • Configure the Cache when constructing OkHttpClient. Set the Cache size for the Cache path
    • Configure CacheControl and cache attributes during Request construction
  • Internal implementation of the task queue, improve the efficiency of concurrent access.

  • The realization of InterceptorChain, request and response hierarchical processing

  • OkHttp also provides support for WebSocket.

    • The HTTP protocol has a flaw: communication can only be initiated by the client
    • WebSocket: the server can take the initiative to push information to the client, and the client can also take the initiative to send information to the server, which is a real two-way equal dialogue and belongs to one of the “server push technology”
    • Setting pingInterval will periodically send a message to the server to maintain a long connection

Synchronous request:

Response response = mOkHttpClient.newCall(okRequest).execute();

Asynchronous request: WebSocke Long connection WebSocket After a TCP connection is established, a handshake is performed over Http, that is, a GET request is sent over Http to the server, telling the server to establish a WebSocket connection. You are ready to do this by adding parameters to the header information. Then the server responds that I know, changes the connection protocol to WebSocket, and starts making long connections.

  • The URL is usuallywsorwssAt the beginning,wsThe correspondingWebsocketAgreement,wssCorresponding to theTLSOn top ofWebSocket. Similar to theHttpandHttpsFor example, WSS ://192.168.1.16.

OKHTTP response header

final class RealCall implements Call { final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor; / /... @Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); Try {// Make a network request client.dispatcher().executed(this); / / after layers of network interceptor, obtain the return value of a network request Response result = getResponseWithInterceptorChain (); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); } } Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); //Application interceptors.addall (client.interceptors()); / / redirection and failed to request interceptor interceptors. Add (retryAndFollowUpInterceptor); // Bridge interceptors.add(new BridgeInterceptor(client.cookiejar ())); Add (new CacheInterceptor(client.internalCache())); // Interceptors. add(new ConnectInterceptor(client)); if (! Interceptor forWebSocket) {/ / network, we can custom the interceptors, for more information than in front interceptors. AddAll (client.net workInterceptors ()); } // Interceptors. add(new CallServerInterceptor(forWebSocket)); // Interceptor.Chain Chain = new RealInterceptorChain(interceptors, NULL, NULL, NULL, 0, originalRequest); return chain.proceed(originalRequest); }}Copy the code

\