[Android] Retrofit source code learning

Keywords: Android Retrofit Java OkHttp

[Android] Retrofit source code learning
The process of using Retrofit#
Create a Retrofit object through Builder:
Copy
Retrofit retrofit = new Retrofit.Builder().baseUrl("").addConverterFactory().build();
Using Java annotation to describe API
Copy
public interface MyApi {

@GET("/api")
Call<Data> getData();

}
Create api object and Call object through retrofit
Copy
MyApi api = retrofit.create(MyApi.class);
Call call = api.getData();
Get data through Call object, enqueue() method sends asynchronous request, and execute() method is used for synchronization
Copy
call.enqueue(new Callback() {

@Override
public void onResponse(Response<ZhuanLanAuthor> author) {
    System.out.println("name:  " + author.getName());
}
@Override
public void onFailure(Throwable t) {
}

});
Principle analysis#
What Retrofit does: translate the Java interface into an HTTP request, and then use OkHttp to send the request.

Retrofit uses dynamic agents to do this

Dynamic agent#
Dynamic proxy can proxy related methods without implementing proxy of the same interface.

Java can implement the Proxy mode through the Proxy class, and the newProxyInstance() method can implement dynamic Proxy. Define agent actions by implementing the InvocationHandler interface.

Proxy.newProxyInstance(ClassLoader, Class<?>[] interfaces,InvocationHandler)
The interface of InvocationHandler is defined as follows:

Copy
public interface InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

}
Parameter meaning:

Proxy: proxy object
Method: proxy method
args: parameter of method
Implement the invoke() method to proxy:

Copy
public Object invoke(Object proxy, Method method, Object[] args)

    throws Throwable {
// do something
method.invoke(target, args);
// do something

}
In this way, the target method can be successfully proxy. The name of the proxy class generated by dynamic proxy is package name + $Proxy+id sequence number

Request process analysis#
Go back to the method of use. At the beginning, use create() to generate the API's objects

Copy
MyApi api = retrofit.create(MyApi.class);
Here's the source code of create():

Copy
public T create(final Class service) {

validateServiceInterface(service); // Judge whether it is an interface
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 it is an Object method, it will be called directly
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        // Compatible with Java 8, Android platform will not call. The way to confirm the platform is to judge the class loading information through the reflection mechanism
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        // Main method, return ServiceMethod object
        return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
      }
    });

}
create uses the method of dynamic proxy and returns the Proxy.newProxyInstance() dynamic proxy object

So the api object is a dynamic proxy object, not a real object generated by the implementation interface. When the api object invokes the getData() method, it is intercepted by the dynamic proxy, and then calls the invoke() method in the InvocationHandler object.

Then Retrofit obtains the annotation information of getData() method through reflection, and creates a ServiceMethod object with the args parameter of invoke()

ServiceMethod passes in the Retrofit object and Method object, calls various interfaces and parsers, and finally generates a Request, including the api domain name, path, http Request Method, Request header, body and so on. Finally, a Call object is returned.

Copy
ServiceMethod<?> loadServiceMethod(Method method) {

ServiceMethod<?> result = serviceMethodCache.get(method); // If there is cache, use it directly
if (result != null) return result;

synchronized (serviceMethodCache) { 
    result = serviceMethodCache.get(method); // Thread safety, check whether other threads are loaded after locking
    if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method); // Analytic annotation
        serviceMethodCache.put(method, result); // Put in Cache
    }
}
return result;

}
Follow up ServiceMethod.parseAnnotation():

Copy
static ServiceMethod parseAnnotations(Retrofit retrofit, Method method) {

// Parsing information about annotation as HTTP request
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) { //API interface method return value cannot be void
    throw methodError(method, "Service methods cannot return void.");
}

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

}
A RequestFactory object is built here to analyze the relevant information about Http protocol in the interface. The specific analysis Method is to get the Annotation of Method and then install of is relatively determined

Copy
RequestFactory(Builder builder) {

method = builder.method;
baseUrl = builder.retrofit.baseUrl;
httpMethod = builder.httpMethod;
relativeUrl = builder.relativeUrl;
headers = builder.headers;
contentType = builder.contentType;
hasBody = builder.hasBody;
isFormEncoded = builder.isFormEncoded;
isMultipart = builder.isMultipart;
parameterHandlers = builder.parameterHandlers;
isKotlinSuspendFunction = builder.isKotlinSuspendFunction;

}
After parsing, use HttpServiceMethod.parseAnnotations() to generate the HttpServiceMethod object

Copy
static HttpServiceMethod parseAnnotations(

Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; // kotlin support, ignore first
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;

Annotation[] annotations = method.getAnnotations(); // Annotation of method @ GET @POST @DELETE, etc
Type adapterType;
// ... here is a piece of code about Kotlin support, adapterType
adapterType = method.getGenericReturnType(); // Return type of interface method, generally call < T >

CallAdapter<ResponseT, ReturnT> callAdapter =
    createCallAdapter(retrofit, method, adapterType, annotations);
Type responseType = callAdapter.responseType();
if (responseType == okhttp3.Response.class) {
    throw methodError(method, "'"
                      + getRawType(responseType).getName()
                      + "' is not a valid response body type. Did you mean ResponseBody?");
}
if (responseType == Response.class) {
    throw methodError(method, "Response must include generic type (e.g., Response<String>)");
}
// HEAD request has no Response Body
if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
    throw methodError(method, "HEAD method must use Void as response type.");
}
// Set the Response resolution, which can be json resolution
Converter<ResponseBody, ResponseT> responseConverter =
    createResponseConverter(retrofit, method, responseType);

okhttp3.Call.Factory callFactory = retrofit.callFactory; // If it is not self-defined, it defaults to OkHttpClient
if (!isKotlinSuspendFunction) {
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); // If you don't use Kotlin, just focus here
}
// ... about Kotlin's return

}
Finally, the inherited class calladapter of HttpServiceMethod is returned, where RequestFactory, Converter and CallFactory are stored

Then let's go back to this code

Copy
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
The invoke method called here is the invoke method in HttpServiceMethod:

Copy
@Override final @Nullable ReturnT invoke(Object[] args) {

Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);

}
//CallAdapted
@Override protected ReturnT adapt(Call call, Object[] args) {

return callAdapter.adapt(call);

}
OKHttpCall here is the encapsulation class of Okhttp3.Call, and implements the Call related methods enqueue and execute.

The last adapt method used here calls callAdapter.adapt() in the Retrofit object to adapt the Call object.

If CallAdapter is not set when initializing Retrofit object, Call will be used by default. When api interface is defined, the return type of method can only be Call

So you can interpret the following code:

Copy
Call call = api.getData();
The api object is a dynamic proxy object. When executing getData(), it enters the dynamic proxy function, and finally calls HttpServiceMethod.invoke(args) at the invoke function of InvocationHandler, and returns a Call object.

Response process analysis#
Retrofit finally calls the custom API interface method to return the Call object, which is actually the OkHttpCall object encapsulated by Retrofit itself. Then we use the enqueue method to issue asynchronous requests.

Copy
call.enqueue(new CallBack() {

@Override
public void onResponse(Call<MyData> call, Response<MyData> response) {
    //... on response    
}
@Override
public void onFailure(Call<MyData> call, Throwable t) {
    //... on response    
}

})
Follow up the source code of OkHttpCall.enqueue:

Copy
@Override public void enqueue(final Callback callback) {

Objects.requireNonNull(callback, "callback == null"); // callback cannot be null

okhttp3.Call call; // Call object of okhttp3
Throwable failure;

synchronized (this) { // Thread safety
    if (executed) throw new IllegalStateException("Already executed.");
    executed = true;

    call = rawCall; // Call object of oktttp3 saved by rawCall for OkHttpCall
    failure = creationFailure;
    if (call == null && failure == null) {
        try {
            // callFactory.newCall(requestFactory.create(args)) is used in createRawCall
            // It's actually OkHttpClient.newCall(OkHttp3.Request)
            // OkHttp3.Call object returned
            call = rawCall = createRawCall();
        } catch (Throwable t) {
            throwIfFatal(t);
            failure = creationFailure = t;
        }
    }
}

if (failure != null) {
    callback.onFailure(this, failure);
    return;
}

if (canceled) {
    call.cancel();
}
// enqueue method using okhttp3.Call
call.enqueue(new okhttp3.Callback() {
    @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)           {
        Response<T> response;
        try {
            // Converter is used to resolve the Response
            // Resolve Okhttp3.Response object to Response object encapsulated by Retrofit
            response = parseResponse(rawResponse);
        } catch (Throwable e) {
            throwIfFatal(e);
            callFailure(e);
            return;
        }

        try {
            // Call the incoming callback
            callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
            throwIfFatal(t);
            t.printStackTrace();
        }
    }

    @Override public void onFailure(okhttp3.Call call, IOException e) {
        callFailure(e);
    }
    // Enter the OnFailure method of callback if the request fails
    private void callFailure(Throwable e) {
        try {
            callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
            throwIfFatal(t);
            t.printStackTrace();
        }
    }
});

}
Where parseResponse() method:

Copy
Response parseResponse(okhttp3.Response rawResponse) throws IOException {

ResponseBody rawBody = rawResponse.body();

// Separate Response Body and response header
// Then deal with the Body
rawResponse = rawResponse.newBuilder()
    .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
    .build();

int code = rawResponse.code(); // HTTP status code
// Unsuccessful response
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();
    }
}
// No content in response, fill in null
if (code == 204 || code == 205) {
    rawBody.close();
    return Response.success(null, rawResponse);
}
// Save the Response Body of the source, which can be used when the resolution fails
ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
    // Use the responseConverter to parse the Body
    T body = responseConverter.convert(catchingBody);
    // Load the resolved Body into the Response object of Retrofit to return
    return Response.success(body, rawResponse);
} catch (RuntimeException e) {
    catchingBody.throwIfCaught();
    throw e;
}

}
The main parse process is to separate the Body and Header of the Okhttp.Response object. If the request is successful and the Body has content, the Body will be handed over to the responseConverter to be parsed into the Response object and loaded into the Response object of the Retrofit to return.

Sum up
Retrofit features: API is declared by using annotation to define API interface. Through annotation parsing, the parsed information is encapsulated in RequestFactory. When using, create() method is called to generate Okhttp Request object.
Through the way of dynamic proxy, proxy the user-defined API interface method to generate the encapsulated OkHttpCall object
Encapsulate okhttp.Call as OkHttpCall, enabling it to use calladapter (which can adapt the returned Call object to other objects, such as the object in rxjava (Unused)) and responseconverter (which supports parsing such as Gson)
At present, it is only read here. There are still some mechanisms to be read
Reference article#
https://www.jianshu.com/p/c1a3a881a144

https://segmentfault.com/a/1190000006767113

https://yq.aliyun.com/articles/658544

Author: y4ngyy

Source: https://www.cnblogs.com/y4ngyy/p/12530566.html

Posted by jdiver on Fri, 20 Mar 2020 04:34:57 -0700