Retrofit official documentation
A type-safe HTTP client for Android and Java
An overview of the
- Retrofit is not a web request framework per se, but rather an encapsulation ofit
- The App application requests through the Retrofit network, essentially encapsulating the request parameters using the Retrofit interface layer, and then OkHttp does the subsequent request operations
- After the server returns the data, OkHttp hands the raw results to Retrofit, which parses the results based on the user’s needs
Type safety
- Retrofit can detect errors at compile time, such AS AS code alerts
- There will be no errors at run
use
- Add Retrofit library dependencies,
- Add network permissions Create classes that receive data from the server (entity beans)
- Create an interface to describe network requests
- Create a Retrofit instance
- Create a network request interface instance
- Sending network requests (asynchronous/synchronous)
- Process the data returned by the server
code
- 1. Create an interface as a WebService request collection, and use annotations to write request methods that need to be configured
interface GitHubService { @GET("users/{user}/repos") fun listRepos(@Path("user") user: String?) : Call<List<Repo>> // use HTTP annotation @http (method = "get",path = "users/{user}/repos",hasBody = false) fun httpRepos(@Path("user") user: String?) : Call<List<Repo>> }Copy the code
- 2. Create an instance of an interface using Retrofit in formal code
- 3. Invoke the corresponding method of the created Service instance to create a corresponding Call object that can be used to initiate network requests
- 4. Use call.execute () or call.enqueue () to initiate the request
class RetrofitActivity:AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.layout_retrofit); Val retrofit = Builder().baseurl ("https://api.github.com/") AddConverterFactory (GsonConverterFactory. The create ()). AddCallAdapterFactory (RxJava2CallAdapterFactory. The create ()) / / RXJava support .build() //2. Proxy object access interface, was introduced into the bytecode val service = retrofit. The create (GitHubService: : class. Java) / / 3 for specific request business methods val repos: Call<List<Repo>> = service.listRepos("octocat") //4 Initiate an asynchronous request repos. Enqueue (object: Callback<List<Repo>? > { override fun onFailure(call: Call<List<Repo>? >, t: Throwable) { println("Response: ${t.message}") } override fun onResponse(call: Call<List<Repo>? >, response: Response<List<Repo>? >) { println("Response: ${response.body()!! [0].name}")}}) Execute () val listReposRx = service.listreposrx ("octocat") listreposrx.subscribe ()}}Copy the code
Static versus dynamic proxies
The proxy pattern: Provides a proxy for other objects to control access to this object
Static agent
Abstract class AbstractObject {protected abstract void operation(); } class RealObject extends AbstractObject {@override protected void operation() {system.out.println ("Do something"); }} class ProxyObject extends AbstractObject {RealObject RealObject; public ProxyObject(RealObject realObject) { this.realObject = realObject; } @override protected void operation() {system.out.println ("do something before real operation"); if (realObject == null){ realObject = new RealObject(); } realObject.operation(); System.out.println("do something before after operation"); }}Copy the code
A dynamic proxy
Dynamic proxy: Proxy classes create proxies while the program is running, dynamically generated based on configuration in the code. Compared to static proxies, dynamic proxies can easily handle the functions of the proxy class uniformly, without frequently changing the functions of each proxy class.
- No intrusion
- Methods to strengthen
classification
- JDK dynamic proxies: Unlike static proxies, JDK dynamic proxies can only create proxy objects for interfaces
- CGLIB
JDK dynamic proxy example
InvocationHandler
- Each proxy class object is associated with an implementation of the InvocationHandler interface that represents the internal processing logic
- Parameters can be retrieved from the parameters of the Invoke method
- The return value of the Invoke method is returned to the consumer
steps
- Create the interface
- Creating a real object
- Creating a proxy class
- Client use
- Through the Java. Lang. Reflect. Proxy. NewProxyInstance (…). Method to get a proxy object for the real object
- The invoke() method of the handler associated with the proxy object is jumped to when a method implemented by the interface associated with the real object is called through the proxy object
//1. Create interface Subject {void shoping(); } @override public void shoping() {system.out.println ("DSH wants to buy something "); Class Proxy implements InvocationHandler {private Object target; Public Proxy(Object target) {this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy: "+proxy.getClass().getName()); System.out.println("before..." ); method.invoke(target,args); System.out.println("after"); return null; Class Client {public static void main(String[] args) {Subject man = new man (); Proxy p = new Proxy(man); / / by Java. Lang. Reflect.. Proxy newProxyInstance (...). Subject Subject = (Subject) java.lang.reflect.proxy.newProxyInstance (man.getClass().getClassLoader(), man.getClass().getInterfaces(), p); // Invoke the invoke() method of the handler associated with the proxy object; // Invoke () method of the handler associated with the proxy object. System.out.println(subject.getClass().getName())); / / output / / proxy: com. DSH. Imocc. Proxy. Dynamic $Proxy0 / / before... / / DSH going shopping. / / after / / com. DSH imocc. Proxy. Dynamic $Proxy0}}Copy the code
The structure
Eight steps for network requests and seven key member variables
Eight steps
- Create a RetroFIT instance
- Define a network request interface and annotate the methods in the interface
- The network request object is generated through a dynamic proxy
- The network request object is platform adapted by the network request adapter
- Network requests are sent through the network request executor
- Data is parsed through a data converter
- Switch threads through the callback executor
- The user returns results from processing in the main thread
Seven key member variables
public final class Retrofit { ... Private final Map<Method, ServiceMethod<? >> serviceMethodCache = new ConcurrentHashMap<>(); //2. The request Factory default is OkhttpClient final okHttp3.call.factory callFactory; //3. Final HttpUrl baseUrl; // Final List<Converter.Factory> converterFactories; Final List< callAdapterFactory > callAdapterFactories; Final @nullable Executor callbackExecutor; // whether flag bits immediately resolve methods in the interface final Boolean validatembit/s; . }Copy the code
- Retrofit network requests return Call by default. Adding ConvertFactory allows custom entity Calls, and adding CallAdapterFactory allows custom Observable returns, which are common in development
Builder pattern in Retroft & Builder Internal Class analysis
The default converter, adapter, thread scheduler, and so on are created in the constructor through platform.get ()
val retrofit = Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) AddCallAdapterFactory (RxJava2CallAdapterFactory. The create ()) / / RXJava support. The build ()Copy the code
- BaseUrl: Converts a String URL to an HttpUrl object
- AddConverterFactory: set up data parser, GsonConverterFactory. The create () is a Gson assignment operation
- AddCallAdapterFactory: Data adapter factory. Same principle
The build () method
Source code analysis
1. Retrofit. The create method
- Creating an instance of a Service interface through the Retrofit.create(Class) method to make the methods configured in the Service available is at the heart of Retrofit’s code structure.
retrofit.create(GitHubService::class.java)
public <T> T create(final Class<T> service) { validateServiceInterface(service); 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 the method is a method from Object then defer to normal invocation. Class == object.class) {return method.invoke(this, args); } / / if there is a platform in method is executed directly if (platform. IsDefaultMethod (method)) {return platform. InvokeDefaultMethod (method, service, proxy, args); } // Call does two things: generates a real OKHTTP call and joins the request queue. The adapter adapt method is used for thread switching (rXJava conversion). Return loadServiceMethod(method). Invoke (args! = null ? args : emptyArgs); }}); }Copy the code
1.1 Interface for validation service: validateServiceInterface()
Verify that it is an interface and does not inherit from other interfaces
1.2 proxy.newProxyInstance () : Dynamically Proxy to create Service interface instances.
- Inside retrofit.create (), the proxy.newProxyInstance () method is used to create Service instances.
- This method creates an object for the multiple interfaces in the argument (or in Retrofit’s case, the fixed one) that implements each method of all the interfaces, And the implementation of each method is the same: invoke() calls an InvocationHandler member variable inside the object instance and passes in its own method information.
- This essentially implements the proxy logic: the methods in the interface are all handled by a separate InvocationHandler object. Also, the actual implementation of these methods is determined at runtime when the interface instance is generated, not at compile time (although this can already be inferred from the code logic at compile time). This is the specific meaning of the “dynamic proxy mechanism” mentioned on the Internet.
The dynamic proxy actually generates the actual running code similar to the following
class ProxyGithubService implements GitHubService{
InvocationHandler invocationHandler = 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 (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
};
@NotNull @Override
public Call<List<Repo>> listRepos(@org.jetbrains.annotations.Nullable String user) {
Method method = null;
try {
method = GitHubService.class.getDeclaredMethod("listRepos",String.class);
return (Call<List<Repo>>) invocationHandler.invoke(this,method, new String[] { user });
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
Copy the code
Thus, the logic in the Invoke () method is the key to creating Service instances with Retrofit.
2. Invoke core method loadServiceMethod()
LoadServiceMethod (method); loadServiceMethod(method); loadServiceMethod(method);
2.1 Creating ServiceMethod:
- The core is ServiceMethod parseAnnotations
- This line of code is responsible for reading information about the original method on the interface (including return value type, method annotation, parameter type, parameter annotation) and doing a preliminary analysis of this information. What is actually returned is a Callseller.
- So I’m going to get it from the cache that I loaded before,
- Rebuild it and add it to the cache without loading it
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; }Copy the code
Trace the code to the ServiceMethod, which is parsed step by step as shown in 2.2 2.3
abstract class ServiceMethod<T> {
static <T> ServiceMethod<T> 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);
}
abstract @Nullable T invoke(Object[] args);
}
Copy the code
2.2 analytical annotations – > RequestFactory. ParseAnnotations – > RequestFactory generated
When loadServiceMethod is used in Retrofit.create (), the RequestFactory constructs a RequestFactory object from the Builder and returns it, which is then used for network requests
- The build process includes the parseMethodAnnotation(annotation), parseParameter, parseHeader, and so on, and finally generates the request and returns all the parameters required
2.3 CallAdapted object generated – > HttpServiceMethod parseAnnotations
- ServiceMethod.parseAnnotations(this, method) -> HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory)
- Here we actually construct a CallCaller object
. if (! isKotlinSuspendFunction) { return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); }...Copy the code
Create OkHttpCall
After loadServiceMethod(Method) returns a CallStuck object, invoke () is invoked
loadServiceMethod(method).invoke(args ! = null ? args : emptyArgs);Copy the code
Keep tracking the source code, which brings us to HttpServiceMethod
- OkHttpCall is a subclass of Retrofit2.Call.
- This line of code is responsible for encapsulating the information read by ServiceMethod (primarily a RequestFactory, an OkHttpClient, and a ResponseConverter) into OkHttpCall;
- This object can create an okHttp3.call object using RequestFactory and OkHttpClient when needed (for example, when its enqueue() method is called). The okHttp3. Call object is called to initiate network requests, such as asynchronous enquue.
- The result is then preprocessed using ResponseConverter and sent back to Retrofit’s Callback.
abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT> { ... @Override final @Nullable ReturnT invoke(Object[] args) { Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter); return adapt(call, args); }... }Copy the code
2.5 OkHttpCall enqueue() method
-
The most important thing in the following code is createRawCall(), which creates the OKHTTP call and then uses the call to call OKHTTP’s enqueque to make the actual network request, returning the value back to the callback.
@Override public void enqueue(final Callback<T> callback) { Objects.requireNonNull(callback, "callback == null"); okhttp3.Call call; Throwable failure; synchronized (this) { if (executed) throw new IllegalStateException("Already executed."); executed = true; call = rawCall; failure = creationFailure; If (call == null && failure == null) {try {//1. CreateRawCall create okHTTP call call = rawCall = createRawCall(); } catch (Throwable t) { throwIfFatal(t); failure = creationFailure = t; } } } if (failure ! = null) { callback.onFailure(this, failure); return; } if (canceled) { call.cancel(); } call.enqueue(new okhttp3.Callback() { @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) { Response<T> response; Response = parseResponse(rawResponse); } catch (Throwable e) { throwIfFatal(e); callFailure(e); return; } try { callback.onResponse(OkHttpCall.this, response); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); // TODO this is not great } } @Override public void onFailure(okhttp3.Call call, IOException e) { callFailure(e); } private void callFailure(Throwable e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); // TODO this is not great } } }); }Copy the code
-
So here we have the network communication result, of course, the final result of the parseResponse(rawResponse) transformation, which was added by addConverterFactory at initialization
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); rawResponse = rawResponse.newBuilder() .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) .build(); . ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody); try { T body = responseConverter.convert(catchingBody); return Response.success(body, rawResponse); } catch (RuntimeException e) { ... }}Copy the code
-
ResponseConverter source
- Trace the source code, responseConverter from HttpServiceMethod before CallBest-name was created
2.6Adapt ()
Call
call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter); Once you have the result of the OKHTTP request, proceed with the adapt(Call, ARGS) method
- This method uses a CallAdapter object to transform the OkHttpCall object to generate a new object. By default, an ExecutorCallbackCall is returned, which cuts the operations back to the main thread and gives them to the Callback.
- In addition, if there is a custom CallAdapter, other types of objects can be generated, such as an RxJava Observable, to allow Retrofit to be used in conjunction with RxJava.
Where does the callAdapter come from?
- When retrofit builds (), a default callAdapter is created and placed in a callAdapterFactories container
- The user-defined callAdapter needs to be passed in when the RetroFIT object is built
Finally, the process is summarized as follows
The end of the I
Summary: Retrofit creates instances of okHTTP network requests for interfaces through dynamic proxies. Its internal interception of interface parameters, annotation parsing and other ways to obtain HTTP network request required by various information, OKHTTP request results through data conversion and thread scheduling back to the caller.
summary
- Retrofit creates instances for the interface to make OKHTTP network requests through dynamic proxies
- The Retrofit core method is loadServiceMethod(Method).invoke(args)
- LoadServiceMethod (Method).Invoke (Args) This consists of loadServiceMethod(Method) and Invoke (Args)
loadServiceMethod(method)
- LoadServiceMethod (method) method calls the ServiceMethod. ParseAnnotations (…). And return a RequestFactory object
- . This method first calls the RequestFactory parseAnnotations (retrofit, method) method, this method through RequestFactory actually build method builds a RequestFactory object, This object contains the information required for the HTTP request by parsing annotation information (including return value type, method annotation, parameter type, parameter annotation)
A CallCaller object is then constructed from the returned RequestFactory object
// Parameter 1, containing the request information requestFactory // parameter 2, which is actually OkHttpClient // parameter 3, data converter // parameter 4, Packaging the Android default MainThreadExecutor DefaultCallAdapterFactory object or RxJava2CallAdapterFactory CallAdapted < > (requestFactory, callFactory, responseConverter, callAdapter);Copy the code
Invoke (args) The invoke method creates an OkHttpCall object that makes a network request through okHTTP
- Adapt method: This method uses a CallAdapter object to transform the OkHttpCall object to generate a new object. By default, an ExecutorCallbackCall is returned, which cuts the operations back to the main thread and gives them to the Callback.
- In addition, if there is a custom CallAdapter, other types of objects can be generated, such as an RxJava Observable, to allow Retrofit to be used in conjunction with RxJava.
Annotations in Retrofit
Request method class
Parameters of the class
Tag class
Design patterns in Retrofit
1. Builder mode
Both Retrofit object creation and ServiceMethod object creation use Build mode to separate the creation and presentation of complex objects. Callers do not need to know the complex creation process, and use Build related methods to configure the creation of objects. Such as Retrofit. Builder, etc
2. Appearance mode
Retrofit provides uniform scheduling externally, shielding internal implementations, making it easy and convenient to use the network library. Facade pattern: Provides a unified interface to access multiple different interfaces of multiple subsystems. It provides a unified high-level interface for a set of interfaces in a subsystem. Using subsystems is easier to use
3. Proxy mode
Retrofit core code proxy.newProxyInstance () Dynamic Proxy
Benefit: The proxy helps us implement the interface without having to write the interface implementation
4. Others: Adapter mode, factory mode, Policy mode, singleton mode
Adapter pattern, factory pattern
Retrofit2.CallAdapter Class adapter pattern
public interface CallAdapter<R, T> { ... T adapt(Call<R> call); . }Copy the code
Factory mode: Returns different Adapters
abstract class Factory { public abstract @Nullable CallAdapter<? ,? > get(Type returnType, Annotation[] annotations, Retrofit retrofit); protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); } protected static Class<? > getRawType(Type type) { return Utils.getRawType(type); }}Copy the code
The strategy pattern
The Retrofit2. Converter class uses the policy pattern
Singleton pattern (Hungrier)
Retrofit2. Platform type
class Platform { private static final Platform PLATFORM = findPlatform(); static Platform get() { return PLATFORM; }... }Copy the code
Retrofit interview questions
- Retrofit thread switching problem
- In the MainThreadExecutor
Handler handler = new Handler(Looper.getMainLooper());
It’s actually scheduling through handler
- In the MainThreadExecutor
- How do RXJava and RetroFIT combine for network requests
- through
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
Add the RXJava adapter - The Network request interface creates an Observable
- Thread scheduling via the RXJava operator
- through
- Hook and dynamic proxy
- Hook: Change the face of a thing by some means, so as to hijack the target of Hook in order to achieve the purpose of controlling the target’s behavior.
Refer to appendix
The most detailed source code parsing for Retrofit