Although a person I am not lonely in my heart you accompany me to see every sunrise
Don’t always be so unwilling to be lonely, because you can let yourself have a lot of thoughts, these thoughts will become your goal and motivation. (opening push a song, fierce poke “accompany me to watch the sunrise” ^_^)
Project – SimpleRetrofit address: https://github.com/ms-liu/SimpleRetrofit/tree/master
The preface
Retrofit [‘retroʊfɪt] Improvement n. To improve the
Why put the English word Retrofit first? A: Just to improve your English ([Leering]).Copy the code
One of the most difficult aspects of coding is naming a class, because a good name is a good way to know what a class does and what a method does. So I think it’s important to let you know what the word Retrofit means before we start dissecting it. Because that’s one word that really describes Retrofit, which is kind of the core idea. So let’s get started, and because this article is going to be a long one, I recommend that you open up Retrofit source code or SimpleRetrofit source code on your computer if you can.
A, how to use?
I’m sure the people who read this article are good programmers who have already used Retrofit and are interested in learning. Here just want to remind you of the use of the impression, hope to have a patient look, to prevent the following analysis will let yourself in the meng state. The Demo:
// New build Retrofit Builder
Retrofit.Builder builder = new Retrofit.Builder();
// Add request BaseURL, transform factory, request adaptation factory, call build method, build Retrofit object
Retrofit retrofit = builder
.baseUrl(API_URL)
.addConverterFactory(new DefaultConverterFactory())
.addCallAdapterFactory(DefaultCallAdapterFactory.INSTANCE)
.build();
//Retrofit calls the create object to create an API object
Weather weather = retrofit.create(Weather.class);
// Call the method in the API interface to get the Call object
Call<String> call = weather.getWeather("%E5%98%89%E5%85%B4&"."json"."5slgyqGDENN7Sy7pw29IUvrZ");
// Call the Call request method,
call.enqueue(new Callback<String>() {
// Get the corresponding result
@Override
public void onResponse(Call<String> call, Response<String> response) {
String body = response.body();
System.out.println("= = = = = = = = = = = = = = ="+body);
}
// Get the failure result
@Override
public void onFailure(Call<String> call, Throwable throwable) {
System.err.println("Exception:"+throwable.toString()); }});Copy the code
Second, the analysis of
1. Master Retrofit
Take a look at the picture below to get a sense of the process and get it in your mind.
(I) Retrofit constructor
Go into a Retrofit class and look at its constructor first (after all, to use a class, the first thing we want to do is get its object, so look at its constructor first).
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,
Executor callbackExecutor, boolean validateEagerly) {
this.callFactory = callFactory;
this.baseUrl = baseUrl;
this.converterFactories = unmodifiableList(converterFactories); // Defensive copy at call site.
this.adapterFactories = unmodifiableList(adapterFactories); // Defensive copy at call site.
this.callbackExecutor = callbackExecutor;
this.validateEagerly = validateEagerly;
}Copy the code
It is not hard to see that he needs a large number of construction parameters, and it is undoubtedly painful and disorganized to create such objects. So Retrofit chose not to provide this constructor externally, so how does he create objects? The answer is to choose The Builder mode to pass in the required parameters step by step to make the whole build process clear and organized.
(2) Retrofit internal class — Builder
In Builder, the required construction parameters of Retrofit constructor are added separately, and some parameters can be implemented by default or initialized. The main parts are as follows:
- BaseUrl (String baseUrl) — adds the baseUrl
- Client (OkHttpClient client) — Add an OkHttpClient object
- AddConverterFactory () – Adds the factory class that produces Converter
- AddCallAdapterFactory () — Adds the factory class that produces the CallAdapter
A brief description of the two factory classes:
- Converter.Factory
So let’s start thinking about, what do we need to do with a request? That’s right! RequestBody and ResponseBody (^_^); So all you need to do in Converter.Factory is to create these two corresponding requirements converters, and the Factory is the inner class in the Converter interface. So Converter.Factory is a Factory or workshop that produces RequestBodyConverter and ResponseBodyConverter. Remember!! It’s enough to know about this plant.
/ * * * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * class name: Converter(Interface) contains an abstract factory class that creates converters by calling different "processing methods". * type * T F = > prior to the conversion = > transformed type * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * /
public interface Converter<F.T> {
/** * how to convert *@param f
* @return
* @throws IOException
*/
T convert(F f) throws IOException;
abstract class Factory{
ResponseBody ->?
public Converter<ResponseBody,? > responseBodyConverter(
Type type, Annotation[] annotations, Retrofit retrofit){
return null;
}
// Create a RequestBody converter? -> RequestBody
public Converter<?, RequestBody> requestBodyConverter(
Type type,Annotation[] parameterAnnotations,Retrofit retrofit){
return null;
}
// Create String converter? -> String
public Converter<?,String> stringConverter(Type iterableType, Annotation[] parameterAnnotations, Retrofit retrofit){
return null; }}}Copy the code
- CallAdapter.Factory
This factory is used to produce calladPters. Calladapters are adapter interfaces that adapt one Call to another. This Call is used to actually initiate and send the network request and receive the request callback. So we can follow specific requirements (e.g., callback handling…). Customize the Call and adapt it through the ADPTE method in the CallAdapter. Calladapter. Factory is used to create a CallAdapter to meet the requirements.
public interface CallAdapter<R,T> {
Type responseType();
T adapt(Call<R> call);
abstract class Factory{
public abstractCallAdapter<? ,? > get(TypereturnType, Annotation[] annotations, Retrofit retrofit); }}Copy the code
So, if we want to tailor calls to requirements and be able to work in Retrofit, we need to go through:
1, CustomCall
2, CustomCallAdapter that returns a custom CustomC through the ADPTE methodall
3, CustomCallAdapterFactory, which returns a custom CustomC through the get methodallAdapterCopy the code
(3) The core Retrofit method create();
All we did was build a large factory (with two important workshops: ConverterFactory and CallAdapterFactory) and build Retrofit. How does Retrofit, which we produce, translate raw materials (defined apis) into products (web request results)? The answer is the Create () method in the Retrofit class. Post code: (This may be slightly different if you choose to look at the source code, as this is my code adaptation)
@SuppressWarnings("unchecked")// Resolve the problem of generics not checking for unity when the agent creates objects
public <T> T create(final Class<T> apiService){
if(! apiService.isInterface())throw new IllegalArgumentException("An API must be defined as an interface.");
if (apiService.getInterfaces().length > 0)
throw new IllegalArgumentException("The API definition does not allow inheritance.");
LogUtils.log(5. Retrofit provides a create() method that creates API objects through dynamic proxies.);
return (T) Proxy.newProxyInstance(
apiService.getClassLoader(),
newClass<? >[]{apiService},new InvocationHandler() {
private final Platform platform = Platform.get(a); @Overridepublic Object invoke(Object o, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class)
return method.invoke(this,args);
// Load the processing API interface method
ServiceMethod<Object.Object> serviceMethod =
(ServiceMethod<Object.Object>) loadServiceMethod(method);
/ / create OkHttpCall
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
// ADAPTS custom calls that are expected to return through the corresponding CallAdapter
returnserviceMethod.mCallAdapter.adapt(okHttpCall); }}); }Copy the code
In this method, load the ServiceMethod (what the hell? Not in a hurry, more on that later), to handle various annotations and parameter values in the API interface. The Call adapter ADAPTS to OkHttpCall to get the desired Call or object. Now, if we look at this picture, is the logic pretty good? If not, go back to the source code for the Retrofit class yourself.
2. Understand the ServiceMethod class
Since the ServiceMethod in the original Retrofit is relatively complex, the explanation may be complicated and confusing, so I will mainly extract the ServiceMethod here to explain (since we no longer need to build this wheel, so it is easy to understand). The ServiceMethod class also contains an internal class Builder for building ServiceMethod. Let’s take a look at the Builder step by step.
(1) the construction method of ServiceMethod.Builder
In this construct we mainly get annotations on Retrofit objects and API interface methods.
Builder(Retrofit retrofit, Method method){
LogUtils.log("7, first create API method processing center (ServiceMethod)");
/ / Retrofit instance
this.mRetrofit = retrofit;
// The method defined in the API interface
this.mMethod = method;
// Annotations defined on methods in the API interface --> @get
this.mMethodAnnotations = method.getAnnotations();
// Parameterized annotations in methods in the API interface
this.mParameterTypes = method.getGenericParameterTypes();
// All parameter annotations in methods in the API interface
this.mParameterAnnotationsArray = method.getParameterAnnotations();
}Copy the code
(2) Parsing build method build()
In this method, the most important:
- Create CallAdapter – createCallAdapter ()
- Create ResponseConverter – createResponseConverter ()
- Parse method annotation, get request type, get relative URL — parserMethodAnnotation()
- Parse the parameter annotations and create the corresponding ParameterHandler in the ParameterHandler [] array
- Return the ServiceMethod object, passing in the Builder itself object reference
public ServiceMethod build(a){
this.mCallAdapter = createCallAdapter();
LogUtils.log("10. Get the appropriate response type from the CallAdapter");
this.mResponseType = mCallAdapter.responseType();
if (mResponseType == okhttp3.Response.class || mResponseType == Response.class)
throw new IllegalStateException("Method return type error, need ResponseBody");
this.mResponseConverter = createResponseConverter();
for (Annotation annotation:
mMethodAnnotations) {
LogUtils.log("13. Annotation on parsing method");
parseMethodAnnotation(annotation);
}
if (mMethodType == null)
throw new IllegalStateException("Which request type must be in the interface method: @get, @post...");
int parameterCount = mParameterAnnotationsArray.length;
LogUtils.log("16. Create an array of parameter handlers of the corresponding length based on the number of annotation parameters in the API interface.");
// Create a parameter processor array of the corresponding length based on the number of parameters.
mParameterHandlers = newParameterHandler<? >[parameterCount]; LogUtils.log("Loop through the annotation parameter ====START");
for (int i = 0; i < parameterCount; i++){
Type parameterType = mParameterTypes[i];
Annotation[] parameterAnnotations = mParameterAnnotationsArray[i];
mParameterHandlers[i] = parserParameter(i,parameterType,parameterAnnotations);
}
LogUtils.log("Loop through the annotation parameter ====END");
return new ServiceMethod<>(this);
}Copy the code
Parsing the parameters in an interface method is a matter of iterating through all the parameter annotations, getting the annotation type, and creating a different ParameterHandler for each type. Here is not the post analysis method, I believe that you go to see the source code, certainly can figure out. So I’m going to show you the ParameterHandler class here. ParameterHandler is a class that adds request parameters to network requests.
public abstract class ParameterHandler<T> {
/** * implements method parameter addition method */
abstract void apply(RequestBuilder builder,T value) throws IOException;
/** * ParameterHandler * @param
*/ for @query
static final class Query<T> extends ParameterHandler<T>{
private final String mQueryName;
private final Converter<T.String> mValueConverter;
private final boolean mUrlEncode;
Query(String name, Converter<T.String> valueConverter, boolean urlEncode){
this.mQueryName = name;
this.mValueConverter = valueConverter;
this.mUrlEncode = urlEncode;
}
@Override
void apply(RequestBuilder builder, T value) throws IOException {
if (mValueConverter == null) return;
// Add request parametersbuilder.addQueryParams(mQueryName,mValueConverter.convert(value),mUrlEncode); }}}Copy the code
(c) get the ServiceMethod object, should do?
- toRequest(Object… Args) adds the parameters in the Request to the Request Request via ParameterHandler and returns the Request
- ToResponse (ResponseBody rawResponseBody) converts the original OKHttp ResponseBody through Converter
public ServiceMethod(Builder<R, T> builder) {
this.mCallFactory = builder.mRetrofit.mCallFactory;
this.mCallAdapter = builder.mCallAdapter;
this.mResponseConverter = builder.mResponseConverter;
this.mBaseUrl = builder.mRetrofit.mBaseUrl;
this.mRelativeUrl = builder.mRelativeUrl;
this.mMethod = builder.mMethod;
this.mMethodType = builder.mMethodType;
this.mParameterHandlers = builder.mParameterHandlers;
}
public Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(mMethodType, mBaseUrl, mRelativeUrl);
@SuppressWarnings("unchecked")
ParameterHandler<Object>[] mParameterHandlers = (ParameterHandler<Object>[]) this.mParameterHandlers; int argumentCount = args ! =null ? args.length : 0;
if(argumentCount ! = mParameterHandlers.length){throw new IllegalStateException(
"Number of parameters to be processed ("+argumentCount+") and parameter number of processors ("+mParameterHandlers.length+") does not correspond"
);
}
for (int i=0; i<argumentCount; i++){ mParameterHandlers[i].apply(requestBuilder,args[i]); }return requestBuilder.build();
}
public R toResponse(ResponseBody rawBody) throws IOException {
return mResponseConverter.convert(rawBody);
}Copy the code
Here’s another illustration of the ServiceMethod class to help you understand it.
Again, OkHttpCall
OkHttpCall is a class encapsulated inside Retrofit that encapsulates the OkHttpCall (for sending or executing network requests) object. It is the actual sender of the Retrofit network request delivery process and the first recipient of the callback of the network request results. OkHttpCall is the Call (OkHttp) proxy class. In addition, OkHttpCall also does whether the request is initiated, whether to cancel the status of the interpretation and listening. Of course, in the current case of this extraction is not reflected, you can go to the actual source code to view. It’s not hard to see.
public class OkHttpCall<T> implements Call<T> {
private finalServiceMethod<T, ? > mServiceMethod;private final Object[] mArgs;
private okhttp3.CallmRawCall; OkHttpCall(ServiceMethod<T,? > serviceMethod, Object [] args){ LogUtils.log(Create an OkHttpCall object);
this.mServiceMethod = serviceMethod;
this.mArgs = args;
}
@Override
public Response<T> execute() throws IOException {
okhttp3.Call call = null;
synchronized (this) {call = mRawCall;
if (call= =null) {call = mRawCall = createRawCall();
}
}
LogUtils.log(Execute (); internal Call OkHTTP3.);
return parserResponse(call.execute());
}
@Override
public void enqueue(final Callback<T> callback) {
okhttp3.Call call = null;
synchronized (this) {call = mRawCall;
if (call= =null) {try {
call = mRawCall = createRawCall();
} catch(IOException e) { e.printStackTrace(); }}}if (call= =null){
callback.onFailure(OkHttpCall.this.new Throwable("Failed to create request object: call == null"));
return;
}
LogUtils.log("18, Call enqueue(with request callback listener), internal Call okHttp3.call enqueue()");
call.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
callback.onFailure(OkHttpCall.this,e);
}
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
try {
// Give a little bit of delay
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Response<T> tResponse = parserResponse(response);
callback.onResponse(OkHttpCall.this,tResponse); }}); } @Overridepublic synchronized Request request()throws IOException {
okhttp3.Call call = mRawCall;
if (call! =null) {return call.request();
}
return (mRawCall = createRawCall()).request();
}
private Response<T> parserResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
int code = rawResponse.code();
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(); }}if (code == 204 || code == 205) {
rawBody.close();
return Response.success(null, rawResponse);
}
LogUtils.log(Call servicemethod.toResponse () to convert okhttp.responseBody to the desired object class.);
T body = mServiceMethod.toResponse(rawBody);
return Response.success(body, rawResponse);
}
private okhttp3.Call createRawCall() throws IOException {
Request request = mServiceMethod.toRequest(mArgs);
okhttp3.Call call = mServiceMethod.mCallFactory.newCall(request);
if (call= =null)
throw new NullPointerException("Cannot create request object");
return call; }}Copy the code
To sort out the logic, let’s take a look at the log output from a simple Retrofit network request:
System.err: = = = = = = = = = = = = >0Create Retrofit Builder object System.err: = = = = = = = = = = = = >1Add BaseUrl System.err: = = = = = = = = = = = = >2, add format conversion factory System.err: = = = = = = = = = = = = >3, add adaptation factory System.err: = = = = = = = = = = = = >4Build Retrofit object System.err: = = = = = = = = = = = = >5Retrofit provides the create() method, which creates the API object System through dynamic proxy.err: = = = = = = = = = = = = >6, load the API interface method processing center (ServiceMethod), and cache (preferentially load the cache) System.err: ============> Create and cache the System for the first time.err: = = = = = = = = = = = = >7Create the API interface method handling center (ServiceMethod) System.err: = = = = = = = = = = = = >8Create adapter System in ServiceMethod.err: = = = = = = = = = = = = >9Create the CallAdapter System by building the CallAdapterFactory added in Retrofit.err: = = = = = = = = = = = = >10, obtain the CallAdapter, suitable for the response type System.err: = = = = = = = = = = = = >11Create request response converter System.err: = = = = = = = = = = = = >12Create the request response converter System by building the ConverterFactory that Retrofit added.err: = = = = = = = = = = = = >13Annotate System on the parse method.err: = = = = = = = = = = = = >14, judge the annotation and get the request type System.err: = = = = = = = = = = = = >15And obtain the relative URL path System.err: = = = = = = = = = = = = >16According to the method in the API interface, annotate the number of parameters, create the corresponding length of parameter processor array System.err: ============> loop through the annotation parameters ====START System.err: = = = = = = = = = = = = > > > > > > > according to API interface methods of each annotation parameters, create the corresponding parameters of the processor System.err: = = = = = = = = = = = = > > > > > > > judgment parameter annotation type is Query, create parameters corresponding to the Query processor System.err: = = = = = = = = = = = = > > > > > > > according to API interface methods of each annotation parameters, create the corresponding parameters of the processor System.err: = = = = = = = = = = = = > > > > > > > judgment parameter annotation type is Query, create parameters corresponding to the Query processor System.err: = = = = = = = = = = = = > > > > > > > according to API interface methods of each annotation parameters, create the corresponding parameters of the processor System.err: = = = = = = = = = = = = > > > > > > > judgment parameter annotation type is Query, create parameters corresponding to the Query processor System.err: ============> loop through the annotation parameter ====END System.err: = = = = = = = = = = = = >17Create OkHttpCall object System.err: = = = = = = = = = = = = >18Call enqueue(with request callback listener), internally Call enqueue() System of okHttp3.call.err: = = = = = = = = = = = = >19Call servicemethod.toResponse () and convert OkHttp.ResponseBody to the desired object class System.out: ===============> Result: {"status":201."message":"APP is disabled by user, please unban it on console"}Copy the code
Iii. Review and summary
Now, I don’t know if you understand the word Retrofit. Retrofit is a large factory with multiple workshops that work in different ways. In the ConverterFactory we create the Converter, in the CallAdapterFactory we create the CallAdapter, The ServiceMethod dispatches annotations from the API and generates the corresponding ParameterHandler, which adds parameter values to the Request. Finally, we Call the actual OkHttpCall through OkHttpCall to make the network request, and process part of the logic, and return the result. So Retrofit is really just revamping and improving OkHttp, just taking raw materials (APIS), putting them in, and producing the desired product (request results).
At this point, we’ve covered the main points of Retrofit. Now, if you’re still in a state of confusion, you probably haven’t read the entire article. (This is a kind of “sweet” confidence that I wrote the article, and I think the biggest reason is that I really didn’t write it well.) If you are half-informed but have some ideas, don’t worry, if you want to, you can choose to continue reading the article. But it’s better to go through the Retrofit source code for yourself with that little bit in mind, or simply Fit source code. If you don’t understand, you can read the article again or leave a comment in the comments section.
Retrofit is actually not that hard from a “posterity in the shade” perspective because it doesn’t really have much code and design patterns (factories and Builders). But as a developer and designer, it’s really hard. Because Retrofit is really scalable and easy to use, and some of the ideas for dealing with problem code are really hard to come up with and implement without being honed. So the “SAO Year” revolution has not yet succeeded, comrades still need to work hard.
At the end of PS:
In fact, this article is very tangled, a total of three times, I want to give up the idea. Because it feels like there are things that are particularly difficult to articulate, and there are places where you can clearly know what is working. But it is difficult to form written words to express (please show concern to me as a “liberal arts student”). In addition, although this article has been written for a long time, there are many things in Retrofit that I have not expressed, which is a pity. So, several times I want to directly record a video to put out. Because the video can better express some things, those difficult to express, can directly through “this stupid thing”, “this thing”, “, “and some words directly, and people will not be confused, feel the atmosphere will be better. In addition, if you can see this place, if you think I can also parse the source code of the idea, please give you want to understand the framework or project source. Let us know in the comments section. I try to see if I can parse it. Looking at other people’s code is always better than thinking about it.