Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What are the differences between OkHttp and Retrofit

2025-01-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly shows you "what is the difference between OkHttp and Retrofit", the content is easy to understand, clear, hope to help you solve your doubts, the following let the editor lead you to study and learn "what is the difference between OkHttp and Retrofit" this article.

Reference answer:

Both OkHttp and Retrofit are popular open source frameworks for the Internet.

Encapsulation is different:

Retrofit encapsulates specific requests, thread switching, and data conversions.

Retrofit encapsulates okhttp by using proxy, appearance, and policy patterns

OkHttp is a set of request clients encapsulated based on Http protocol.

Different responsibilities:

Retrofit is mainly responsible for application-level encapsulation, for developers, easy to use, such as request parameters, response data processing, error handling and so on.

OkHttp is mainly responsible for the optimization and encapsulation of socket, such as network access, multiplexing, buffer cache, data compression and so on.

(you can easily leave a GitHub link, and those who need to get the relevant interview can find it yourself.)

Https://github.com/xiangjiana/Android-MS

(VX:mm14525201314)

Retrofit and OkHttp can be said to be brothers, they are both network request libraries launched by Square, and Retrofit is actually implemented based on OkHttp, which encapsulates the existing functions of OkHttp, supports the configuration of network request parameters through annotations, and uniformly wraps the parsing and serialization after data return, and even introduces the support for cooperative program pairs recently.

Today let's take a look at how Retrofit elegantly encapsulates and expands its functionality on the basis of a fixed framework like OkHttp.

Basic use

Let's first take a look at the basic use of Retrofit to get a general idea of it.

First, we can build the following request Service class, in which the methods and parameters of each request are annotated:

Public interface GitHubService {@ GET ("users/ {user} / repos") Call listRepos (@ Path ("user") String user);}

After that, we can build a Retrofit object and pass in the corresponding class through the Retrofit.create method to build the corresponding Service object:

Retrofit retrofit = new Retrofit.Builder () baseUrl ("https://api.github.com/") build ();} GitHubService service = retrofit.create (GitHubService.class))

After that, we call the corresponding method in service and we can get the Call object.

The asynchronous call to the request can be achieved by calling enqueue on the Call object, while the synchronous call to the request can be achieved through the execute method.

Construction of Retrofit object

Retrofit is built in Builder mode, and there are a lot of configurations that can be done in Builder, including baseUrl, okhttpClient, converterFactory, callAdapterFactory, and so on.

There's nothing special here, just some simple assignments, so we don't care anymore, we just need to see which parameters are passed in to Retrofit at last. It finally calls the following constructor to initialize the parameter.

Retrofit (okhttp3.Call.Factory callFactory, HttpUrl baseUrl, List converterFactories, List callAdapterFactories, @ Nullable Executor callbackExecutor, boolean validateEagerly) {this.callFactory = callFactory; this.baseUrl = baseUrl; this.converterFactories = converterFactories; / / Copy+unmodifiable at call site. This.callAdapterFactories = callAdapterFactories; / / Copy+unmodifiable at call site. This.callbackExecutor = callbackExecutor; this.validateEagerly = validateEagerly;} creation of Service object dynamic proxy creation Service proxy class

Then we see how the self-defined interface can obtain the instance simply by passing class to Retrofit.create, which is obviously just an interface?

Public T create (final Class service) {/ / A pair of Service interfaces to detect validateServiceInterface (service); / / build proxy objects return (T) Proxy.newProxyInstance (service.getClassLoader (), new Class [] {service}, new InvocationHandler () {private final Platform platform = Platform.get (); private final Object [] emptyArgs = new Object [0]) through dynamic proxy @ Override public @ Nullable Object invoke (Object proxy, Method method, @ Nullable Object [] args) throws Throwable {/ / Object class method calls if (method.getDeclaringClass () = = Object.class) {return method.invoke (this, args) as usual } / / if it is a method that already exists in the class corresponding to the platform itself, call if (platform.isDefaultMethod (method)) {return platform.invokeDefaultMethod (method, service, proxy, args) as usual;} / / otherwise get the corresponding Method through the loadServiceMethod method and invoke return loadServiceMethod (method) .invoke (args! = null? Args: emptyArgs);});}

As you can see, the acquisition of Service objects is actually achieved through dynamic proxies. Here, the interface is first detected by the validateServiceInterface method, and then the interface is proxied by the dynamic proxy.

Methods that are unique to the Object class and exist on the corresponding platform are called as usual, otherwise the corresponding Method object in the Service is processed by loadServiceMethod, and then the invoke method is called on it.

This shows that Retrofit does not parse all the methods in the interface immediately when creating the corresponding object of the Service interface, but adopts the lazy loading idea of parsing only when the method is called.

Then let's take a look at the validateServiceInterface method:

Private void validateServiceInterface (Class service) {/ / determines whether it is an interface if (! service.isInterface ()) {throw new IllegalArgumentException ("API declarations must be interfaces.");} / / determines whether the interface and all interfaces it inherits contain generic parameters, and if so, throws an exception Deque candidate = check.removeFirst () If (candidate.getTypeParameters (). Length! = 0) {StringBuilder message = new StringBuilder ("Type parameters are unsupported on") .append (candidate.getName ()); if (candidate! = service) {message.append ("which is an interface of") .append (service.getName ());} throw new IllegalArgumentException (message.toString ());} Collections.addAll (check, candidate.getInterfaces ()) } / / if the methods of Service are set to be processed urgently when creating the Retrofit, methods that are not unique to the platform and are not static are handled through the loadServiceMethod method. If (validateEagerly) {Platform platform = Platform.get (); for (Method method: service.getDeclaredMethods ()) {if (! platform.isDefaultMethod (method) & &! Modifier.isStatic (method.getModifiers () {loadServiceMethod (method);}

First, this method detects service to ensure that it is an interface and that there are no paradigm parameters in it and its inherited classes.

Later, if validateEagerly is set to true when Retrofit is created, all non-platform-specific and non-static methods in Service will be handled in advance through the loadServiceMethod method.

Parsing of methods in Service so let's see what loadServiceMethod has done: 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;}

First of all, it will try to get the ServiceMethod object from the serviceMethodCache cache in the way of Double Check. If it cannot get it, it uses the ServiceMethod.parseAnnotations method to process the comments of the Method and adds the resulting ServiceMethod object to the cache.

In other words, in order to avoid multiple processing of method annotations, Retrofit uses a serviceMethodCache to cache the parsed ServiceMethod.

Then let's take a look at how the parseAnnotations method parses the annotations of the method.

Static ServiceMethod parseAnnotations (Retrofit retrofit, Method method) {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) {throw methodError (method, "Service methods cannot return void.") } return HttpServiceMethod.parseAnnotations (retrofit, method, requestFactory);}

Here, the annotation is parsed by the RequestFactory.parseAnnotations method and a RequestFactory object is obtained.

After that, requestFactory is passed in through the HttpServiceMethod.parseAnnotations method to parse the annotations and get the ServiceMethod object.

Annotation analysis

Let's first take a look at RequestFactory.parseAnnotations:

Static RequestFactory parseAnnotations (Retrofit retrofit, Method method) {return new Builder (retrofit, method) build ();}

It passes Method into Builder to build a new RequestFactory:

Builder (Retrofit retrofit, Method method) {this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations (); this.parameterTypes = method.getGenericParameterTypes (); this.parameterAnnotationsArray = method.getParameterAnnotations ();}

The annotations contained in the method, the norms contained in the parameters and the annotations of the parameters are obtained by reflection in Builder.

Then take a look at the build method:

RequestFactory build () {for (Annotation annotation: methodAnnotations) {/ / traversal method annotations parse each annotation parseMethodAnnotation (annotation);} / /. Exception handling / / parsing a pair of parameters int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler [parameterCount]; for (int p = 0, lastParameter = parameterCount-1; p < parameterCount; pairing +) {parameterHandlers [p] = parseParameter (p, parameterTypes [p], parameterAnnotationsArray [p], p = = lastParameter);} / /. Exception handling return new RequestFactory (this);}

In the build method, the parseMethodAnnotation is called to parse each annotation of the method, and the parseParamter method is called to parse to the ParamterHandler object for each parameter.

The code for parseMethodAnnotation is as follows:

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;}}

In fact, each type supported by HTTP is supported, the url in the corresponding annotation is obtained, and parseHttpMethodAndPath is called for processing, while the Headers annotation is processed through parseHeaders.

Processing of Http request mode and Path

For Method and Path, the parameters are assigned through parseHttpMethodAndPath:

Private void parseHttpMethodAndPath (String httpMethod, String value, boolean hasBody) {if (this.httpMethod! = null) {throw methodError (method, "Only one HTTP method is allowed. Found:% s and% s.", this.httpMethod, httpMethod);} this.httpMethod = httpMethod; this.hasBody = hasBody; if (value.isEmpty ()) {return;} / / Get the relative URL path and existing query string, if present. Int question = value.indexOf ('?'); if (question! =-1 & & question < value.length ()-1) {/ / Ensure the query string does not have any named parameters. String queryParams = value.substring (question + 1); Matcher queryParamMatcher = PARAM_URL_REGEX.matcher (queryParams); if (queryParamMatcher.find ()) {throw methodError (method, "URL query string\"% s\ "must not have replace block." + "For dynamic query parameters use @ Query.", queryParams);} this.relativeUrl = value; this.relativeUrlParamNames = parsePathParameters (value);}

This actually assigns values to different HTTP request methods and Path, and ensures that there are no parameters in the Path of this interface through regular expressions.

Private Headers parseHeaders (String [] headers) {Headers.Builder builder = new Headers.Builder (); for (String header: headers) {int colon = header.indexOf (':'); if (colon = =-1 | | colon = = 0 | colon = = header.length ()-1) {throw methodError (method, "@ Headers value must be in the form\" Name: Value\ ". Found:\ "% s\", header);} String headerName = header.substring (0, colon); String headerValue = header.substring (colon + 1). Trim (); if ("Content-Type" .equalsIgnoreCase (headerName)) {try {contentType = MediaType.get (headerValue);} catch (IllegalArgumentException e) {throw methodError (method, e, "Malformed content type:% s", headerValue) }} else {builder.add (headerName, headerValue);}} return builder.build ();}

For Headers, the passed Headers list is parsed to the corresponding Headers object.

Processing of method parameters finally we look at the processing of method parameters: private @ Nullable ParameterHandler parseParameter (int p, Type parameterType, @ Nullable Annotation [] annotations, boolean allowContinuation) {ParameterHandler result = null; if (annotations! = null) {for (Annotation annotation: annotations) {/ / a pair of annotations are parsed by parseParameterAnnotation ParameterHandler annotationAction = parseParameterAnnotation (p, parameterType, annotations, annotation) If (annotationAction = = null) {continue;} if (result! = null) {throw parameterError (method, p, "Multiple Retrofit annotations found, only one allowed.");} result = annotationAction }} if (result = = null) {/ / Special treatment for if (allowContinuation) {try {if (Utils.getRawType (parameterType) = = Continuation.class) {isKotlinSuspendFunction = true; return null }} catch (NoClassDefFoundError ignored) {}} throw parameterError (method, p, "No Retrofit annotation found.");} return result;}

The code for the parseParamterAnnotation method is too long, so it is no longer posted here. It uniquely handles each annotation of the method and returns the corresponding ParamterHandler.

It can be found that the main function of RequestFactory.parseAnnotations is to parse the method annotation information, which is used to generate the corresponding Request.

After the creation of ServiceMethod, let's take a look at HttpServiceMethod.parseAnnotations: static HttpServiceMethod parseAnnotations (Retrofit retrofit, Method method, RequestFactory requestFactory) {boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; boolean continuationWantsResponse = false; boolean continuationBodyNullable = false; Annotation [] annotations = method.getAnnotations (); Type adapterType; if (isKotlinSuspendFunction) {/ / if the method is the suspend method Type [] parameterTypes = method.getGenericParameterTypes () in kotlin / / get the paradigm parameter of Continuation, which is the return value type of suspend method Type responseType = Utils.getParameterLowerBound (0, (ParameterizedType) parameterTypes [parameterTypes.length-1]); / / if the paradigm parameter of Continuation is Response, then it needs Response, then set continuationWantsResponse to true; if (getRawType (responseType) = = Response.class & & responseType instanceof ParameterizedType) {/ / Unwrap the actual body type from Response. ResponseType = Utils.getParameterUpperBound (0, (ParameterizedType) responseType); continuationWantsResponse = true;} else {/ / TODO figure out if type is nullable or not / / Metadata metadata = method.getDeclaringClass (). GetAnnotation (Metadata.class) / / Find the entry for method / / Determine if return type is nullable or not} adapterType = new Utils.ParameterizedTypeImpl (null, Call.class, responseType); annotations = SkipCallbackExecutorImpl.ensurePresent (annotations) } else {/ / otherwise get the paradigm parameter of the return value of the method, that is, the type of the return value required by the request is adapterType = method.getGenericReturnType ();} / / create the CallAdapter object CallAdapter callAdapter = createCallAdapter (retrofit, method, adapterType, annotations) through the createCallAdapter method; 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) ");} / / TODO support Unit for Kotlin? If (requestFactory.httpMethod.equals ("HEAD") & &! Void.class.equals (responseType)) {throw methodError (method, "HEAD method must use Void as response type.");} / / create a Converter object by the createResponseConverter method Converter responseConverter = createResponseConverter (retrofit, method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory If (! isKotlinSuspendFunction) {/ / is not a suspend method, then directly create and return a CallAdapted object return new CallAdapted (requestFactory, callFactory, responseConverter, callAdapter);} else if (continuationWantsResponse) {/ / noinspection unchecked Kotlin compiler guarantees ReturnT to be Object. Return (HttpServiceMethod) new SuspendForResponse (requestFactory, callFactory, responseConverter, (CallAdapter) callAdapter);} else {/ / noinspection unchecked Kotlin compiler guarantees ReturnT to be Object Return (HttpServiceMethod) new SuspendForBody (requestFactory, callFactory, responseConverter, (CallAdapter) callAdapter, continuationBodyNullable);}}

The code here is very long and can be roughly summarized into the following steps:

1. If this method is the suspend method in Kotlin, because it is implemented by the co-program, you need to get the Continuation paradigm parameter, which is the true type of the request return value.

two。 If the return value of the suspend method is Response, which means that it needs Response instead of a specific type, set continuationWantsResponse to true

3. If it is not the suspend method, the type of the paradigm parameter of the return value is the real type of the request return value (Call, then ReturnType is the type that is really needed after conversion).

4. Create a CallAdapter object through the createCallAdapter method, which is used to adapt the Call object to the desired type of ReturnT object.

5. After getting the CallAdapter, the type of Response is obtained and verified.

6. Get the Converter object through the createResponseConverter method, which completes the conversion from ResponseBody to Response type ResponseT.

7. If it is not the suspend method of Kotlin, it is passed directly into CallAdapter and Converter to create a CallAdapted object.

8. Otherwise, depending on whether the suspend method requires Response or a specific type, return SuspendForResponse and SuspendForBody objects, respectively.

It can be found that the new version of Retrofit supports the collaborative process of Kotlin. The main role of HttpServiceMethod.parseAnnotations is to create CallAdapter and Converter objects and build the corresponding HttpServiceMethod.

CallAdapter

CallAdapter is used to adapt the Call object to the desired type T object. Its statement is as follows:

Public interface CallAdapter {/ / returns the type Type responseType () of Response; / / converts Call to T type T adapt (Call call);}

Let's first look at how the createCallAdapter method is created for it:

Private static CallAdapter createCallAdapter (Retrofit retrofit, Method method, Type returnType, Annotation [] annotations) {try {/ / noinspection unchecked return (CallAdapter) retrofit.callAdapter (returnType, annotations);} catch (RuntimeException e) {/ / Wide exception range because factories are user code. Throw methodError (method, e, "Unable to create call adapter for% s", returnType);}}

It calls the retrofit.callAdapter method:

Public CallAdapter callAdapter (Type returnType, Annotation [] annotations) {return nextCallAdapter (null, returnType, annotations);}

Then call the retrofit.nextCallAdapter method:

Public CallAdapter nextCallAdapter (@ Nullable CallAdapter.Factory skipPast, Type returnType, Annotation [] annotations) {Objects.requireNonNull (returnType, "returnType = = null"); Objects.requireNonNull (annotations, "annotations = = null"); int start = callAdapterFactories.indexOf (skipPast) + 1; for (int I = start, count = callAdapterFactories.size (); I < count CallAdapterFactories +) {/ / iterate through callAdapterFactories and try to create CallAdapter CallAdapter adapter = callAdapterFactories.get (I) .get (returnType, annotations, this); if (adapter! = null) {return adapter;}} / /. There is no corresponding CallAdapterFactory, throw an exception}

This is actually traversing the list of CallAdapter.Factory passed when the Retrofit object was created in an attempt to create the CallAdapter. If none of these CallAdapter.Factory can handle the corresponding returnType and annotations, an exception will be thrown. (the previous Factory has a higher priority)

There is a default CallAdapter factory DefaultCallAdapterFactory in Retrofit, which has a lower priority than all custom factories. An Executor is passed in when it is created, and we can see its get method:

@ Override public @ Nullable CallAdapter get (Type returnType, Annotation [] annotations, Retrofit retrofit) {if (getRawType (returnType)! = Call.class) {return null;} if (! (returnType instanceof ParameterizedType)) {throw new IllegalArgumentException ("Call return type must be parameterized as Call or Call

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report