1. A common network request process in development
url, parameter -- > request -- > Convert to Http protocol -- > request execution -- > return result -- > Convert to response -- > response convert to our object
Only the head and tail are customized by our daily development, and other processes in the middle are carried out by the network framework.
2. What retrofit does
- 1.url, parameter Retrofit mainly adopts interface + annotation mode
- 2. The intermediate network request framework uses dynamic agent to use a certain framework
- 3. The response is converted to our object and adapts in adapter mode
3. Implementation process of retrofit
1) to request, url and parameter Retrofit mainly adopt interface + annotation mode
Let's take a simple POST request as an example
@POST("ssoService/v1/logoutCTGT") Call<BaseResponseBean> logout(@Body LogoutRequest logoutRequest);
@Documented @Target(METHOD) @Retention(RUNTIME) public @interface POST { String value() default ""; } @Documented @Target(PARAMETER) @Retention(RUNTIME) public @interface Body { }
Analysis of annotation knowledge points
-
Definition of annotation
- @Documented
The Documented annotation indicates that the annotation is recorded by javadoc, and there are similar recording tools by default. If a type declaration is Documented with comments, its comments become part of the public API.
- @Target(METHOD)
Comments may appear in the syntax of Java programs, such as TYPE, FIELD, METHOD, PARAMETER, etc
- @Retention(RUNTIME)
Describes the various policies for retaining comments, source (source phase, ignored during compilation), CLASS (compiled into CLASS file, but not in JVM), RUNTIME (all phases exist)
-
Get annotation (ServiceMethod.Builder class)
Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations(); }
How to get annotations
this.methodAnnotations = method.getAnnotations(); this.parameterAnnotationsArray = method.getParameterAnnotations();
Annotation acquisition is relatively simple. The Method method can obtain various annotations directly, one is the annotation on the Method, the other is the annotation on the parameter
- Retrofit processing of the acquired annotation (ServiceMethod.Builder.build())
for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } ...... int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<?>[parameterCount]; for (int p = 0; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; if (Utils.hasUnresolvableType(parameterType)) { throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", parameterType); } Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) { throw parameterError(p, "No Retrofit annotation found."); } parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); }
After annotation processing, it will be converted to related attributes
private final String httpMethod; private final String relativeUrl; private final Headers headers; private final MediaType contentType; private final boolean hasBody; private final boolean isFormEncoded; private final boolean isMultipart; private final ParameterHandler<?>[] parameterHandlers;
Then convert to Request in ServiceMethod.toRequest method
Request toRequest(@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types. ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers; int argumentCount = args != null ? args.length : 0; if (argumentCount != handlers.length) { throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")"); } for (int p = 0; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); } return requestBuilder.build(); }
The above analysis comments to toRequest, and then we analyze the calling process of the code
Synchronization request process
OkHttpCall.execute()->OkHttpCall.createRawCall()->serviceMethod.toRequest()
Asynchronous process
OkHttpCall.enqueue()->OkHttpCall.createRawCall()->serviceMethod.toRequest()
2)toResponse
It is convenient for us to reverse how to use dynamic agent to call the network framework by using the priority resolution to response
Compared with toRequest, toResponse is much simpler. In our daily work, we often use Gson to transform the results. We analyze the toResponse through the addition of converter and the call process of transformation
- Let's start with when we added the method of transforming into our own object
public static <T> T createJsonApi(@Url String baseUrl, Class<T> service) { if (!checkUrl(retrofitRxJson, baseUrl)) { retrofitRxJson = new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .build(); } return retrofitRxJson.create(service); }
.addConverterFactory(GsonConverterFactory.create())
Using Gson to transform the content returned by the platform into our objects
public class BaseResponseBean<T> { public Integer type; public String code; public String msg; public T data; }
- To response the whole calling process
- Synchronous request
OkHttpCall.excute()-> OkHttpCall.parseResponse()->serviceMethod.toResponse()->serviceMethod.responseConverter.convert()
- Asynchronous request
OkHttpCall.enqueue()-> hik.lib.okhttp3.Callback.onResponse()->OkHttpCall.parseResponse()->serviceMethod.toResponse()->serviceMethod.responseConverter.convert()
- Initialization of responseConverter
ServiceMehtond.Builder.build()->ServiceMehtond.Builder.createResponseConverter()->retrofit.responseBodyConverter()-> retrofit.nextResponseBodyConverter()
< U > here is a better design. See the code < / u >
public <T> Converter<ResponseBody, T> nextResponseBodyConverter( @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) { checkNotNull(type, "type == null"); checkNotNull(annotations, "annotations == null"); int start = converterFactories.indexOf(skipPast) + 1; for (int i = start, count = converterFactories.size(); i < count; i++) { Converter<ResponseBody, ?> converter = converterFactories.get(i).responseBodyConverter(type, annotations, this); if (converter != null) { //noinspection unchecked return (Converter<ResponseBody, T>) converter; } } StringBuilder builder = new StringBuilder("Could not locate ResponseBody converter for ") .append(type) .append(".\n"); if (skipPast != null) { builder.append(" Skipped:"); for (int i = 0; i < start; i++) { builder.append("\n * ").append(converterFactories.get(i).getClass().getName()); } builder.append('\n'); } builder.append(" Tried:"); for (int i = start, count = converterFactories.size(); i < count; i++) { builder.append("\n * ").append(converterFactories.get(i).getClass().getName()); } throw new IllegalArgumentException(builder.toString()); }
< font color = "hotpink" > < U > if there are multiple types of return conversion, they will be matched according to the return type of the interface < / u > < font >
3) use of network framework by dynamic agent
We analyzed the process of calling two methods: toRequest() and toResponse(), as well as the network request from OkHttpCall. Next, we analyzed how the dynamic agent calls the execute() and enqueue() in OkHttpCall
When we use it, we define the interface, but we do not define the implementation class. Here is the implementation class of the interface using the dynamic proxy method
##Here's something to pay attention to******************* The < U > interface returns an object that encapsulates the network request, but not the network request
- Creation of dynamic agent
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public 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); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
In terms of code, dynamic proxy is relatively simple.
Analysis of dynamic agent knowledge points
dynamic proxy By using Java reflection technology, a new class (also known as "dynamic proxy class") and its instances (objects) are created at runtime to implement some given interfaces. The proxy is an interface, not a class or an abstract class. The specific implementation is only known at runtime, which is the principle of spring aop.
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)throws IllegalArgumentException
newProxyInstance, the method has three parameters:
Loader: which class loader to use to load the proxy object
Interfaces: interfaces that need to be implemented by dynamic proxy classes
h: when the dynamic proxy method executes, it will call the invoke method in h to execute
< U > in combination with our knowledge of Retrofit.create method and dynamic agent, that is, all methods in our interface are finally called < / u >
serviceMethod.callAdapter.adapt(okHttpCall);
An object wrapped in OkHttpCall is returned here
Here is another knowledge point
Retrofit
final hik.lib.okhttp3.Call.Factory callFactory; final HttpUrl baseUrl; final List<Converter.Factory> converterFactories; final List<CallAdapter.Factory> adapterFactories; final @Nullable Executor callbackExecutor; final boolean validateEagerly;
Whether the object returned by adapterFactories is RxJava2CallAdapterFactory or the default calladapterfactory
Let's take a look at the initialization of the callAdapter property in serviceMethod
ServiceMethod.Builder.buid()->ServiceMethod.Builder.createCallAdapter->retrofit.callAdapter->retrofit.nextCallAdapter
Upper code
public CallAdapter<?, ?> nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) { checkNotNull(returnType, "returnType == null"); checkNotNull(annotations, "annotations == null"); int start = adapterFactories.indexOf(skipPast) + 1; for (int i = start, count = adapterFactories.size(); i < count; i++) { CallAdapter<?, ?> adapter = adapterFactories.get(i).get(returnType, annotations, this); if (adapter != null) { return adapter; } } StringBuilder builder = new StringBuilder("Could not locate call adapter for ") .append(returnType) .append(".\n"); if (skipPast != null) { builder.append(" Skipped:"); for (int i = 0; i < start; i++) { builder.append("\n * ").append(adapterFactories.get(i).getClass().getName()); } builder.append('\n'); } builder.append(" Tried:"); for (int i = start, count = adapterFactories.size(); i < count; i++) { builder.append("\n * ").append(adapterFactories.get(i).getClass().getName()); } throw new IllegalArgumentException(builder.toString()); }
The last step is to match the adapterfactory according to the return type of the interface
- Next, let's look at the implementation of AdaperFactory
Let's first look at the implementation of a default calladapterfactory
final class DefaultCallAdapterFactory extends CallAdapter.Factory { static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory(); @Override public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { if (getRawType(returnType) != Call.class) { return null; } final Type responseType = Utils.getCallResponseType(returnType); return new CallAdapter<Object, Call<?>>() { @Override public Type responseType() { return responseType; } @Override public Call<Object> adapt(Call<Object> call) { return call; } }; } }
The direct return is OkHttpCall, so the network request is execute, enqueue.
If it is RxJava2CallAdapter, it will return Observable, and then call the network request through RXJava method
public static void asyHttpRequest(Observable observable , BaseNetCallback netCallback){ observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); observable.subscribe(new BaseNetObserver<>(netCallback)); }
Set the execution thread, listen to the result thread, and then initiate signal execution
We're looking at the code in the signal execution
@Override protected void subscribeActual(Observer<? super Response<T>> observer) { // Since Call is a one-shot type, clone it for each new observer. Call<T> call = originalCall.clone(); CallCallback<T> callback = new CallCallback<>(call, observer); observer.onSubscribe(callback); call.enqueue(callback); }
By now, we have basically understood the whole process of Retrofit2
####4. Code summary of retrofit The main code implementation of Retrofit
-
OKHttpCall is mainly where the network request is actually implemented, mainly including
- execute
- enqueue
- createRawCall
-
ServiceMethod
- toRequest
- toResponse
- Builder.build()
-
Retrofit
- create
- nextCallAdapter
- nextResponseBodyConverter