[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