Specific usage analysis of Android Retrofit source code

Specific usage analysis of Android Retrofit source code

[[170834]]

Introduction

Retrofit is an HTTP framework launched by Square, mainly used in Android and Java. Retrofit converts network requests into method calls, which is very simple and convenient to use. This article first briefly introduces the usage of Retrofit, and then specifically analyzes the execution process of its source code.

Basic Usage

Retrofit turns HTTP API into Java interface. Here is an example from Retrofit official website:

  1. public interface GitHubService {
  2. @GET( "users/{user}/repos" )
  3. Call<List<Repo>> listRepos(@Path( "user" ) String user );
  4. }

In the GithubService interface, there is a method listRepos, which is annotated with @GET, indicating that this is a GET request. The users/{user}/repos in the brackets at the end is the path of the request, where {user} indicates that this part is dynamically changing, and its value is passed by the method parameter, and the parameter @Path("user") String user of this method is used to replace {user}. Also note that the return value of this method is Call<List<Repo>>. It can be seen that Retrofit uses annotations to describe the parameters related to a network request.

The above is just the beginning. Now we need to make this network request:

  1. Retrofit retrofit = new Retrofit.Builder()
  2. .baseUrl( "https://api.github.com/" )
  3. .addConverterFactory(GsonConverterFactory. create ())
  4. .build();
  5.  
  6. GitHubService service = retrofit. create (GitHubService. class);
  7. Call<List<Repo>> repos = service.listRepos( "octocat" );
  8. repos.enqueue(new Callback<List<Repo>>() {
  9. @Override
  10. public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
  11.              
  12. }
  13. @Override
  14. public void onFailure(Call<List<Repo>> call, Throwable t) {
  15.  
  16. }
  17. });

It can be seen that a Retrofit object is first constructed, in which the baseUrl parameter is passed in. The baseUrl and the path after the above GET method are combined to form a complete URL. In addition to baseUrl, there is also a converterFactory, which is used to convert the returned http response into a Java object, corresponding to the return value of the method Call<List<Repo>> in List<Repo>>, where Repo is a custom class. With the Retrofit object, call its create method to create an instance of GitHubService, and then call the method of this instance to request the network. Call the listRepo method to get a Call object, and then use enqueue or execute to initiate the request. enqueue is asynchronous execution, while execute is synchronous execution.

This is the basic usage of Retrofit. For other details, please check the official website.

Source code analysis

When I first came into contact with Retrofit, I thought it was quite magical, and its usage was different from general network requests. Let's take a look at how the source code of Retrofit is implemented.

Retrofit creation

From the creation method of Retrofit, we can see that the Builder mode is used. There are several key variables in Retrofit:

  1. //Method used to cache parsed data
  2. private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
  3.   
  4. //The OKHttp factory for requesting the network, the default is OkHttpClient
  5. private final okhttp3.Call.Factory callFactory;
  6.   
  7. //baseurl
  8. private final HttpUrl baseUrl;
  9.   
  10. //A collection of converters for the response obtained by requesting the network. BuiltInConverters will be added by default
  11. private final List<Converter.Factory> converterFactories;
  12.   
  13. //Convert the Call object to other types
  14. private final List<CallAdapter.Factory> adapterFactories;
  15.   
  16. //Used to execute callbacks. The default in Android is MainThreadExecutor
  17. private final Executor callbackExecutor;
  18.   
  19. //Do you need to parse the method in the interface immediately?
  20. private final boolean validateEagerly;
  21.   

Let's take a look at the builder method of the inner class Builder in Retrofit:

  1. public Retrofit build() {
  2. if (baseUrl == null ) {
  3. throw new IllegalStateException( "Base URL required." );
  4. }
  5.  
  6. okhttp3.Call.Factory callFactory = this.callFactory;
  7. if (callFactory == null ) {
  8. //Create an OkHttpClient by default
  9. callFactory = new OkHttpClient();
  10. }
  11.  
  12. Executor callbackExecutor = this.callbackExecutor;
  13. if (callbackExecutor == null ) {
  14. // Android returns MainThreadExecutor
  15. callbackExecutor = platform.defaultCallbackExecutor();
  16. }
  17.  
  18. // Make a defensive copy of the adapters and   add the default Call adapter.
  19. List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  20. adapterFactories.add ( platform.defaultCallAdapterFactory (callbackExecutor));
  21.  
  22. // Make a defensive copy of the converters.
  23. List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
  24.  
  25. return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
  26. callbackExecutor, validateEagerly);
  27. }

When creating Retrofit, if OkHttpClient is not specified, a default one will be created. If callbackExecutor is not specified, the platform default will be returned, which is MainThreadExecutor in Android, and a CallAdapter will be built using this to add to adapterFactories.

create method

Once you have a Retrofit object, you can create an instance of the network request interface class through the create method. The code is as follows:

  1. public <T> T create (final Class<T> service) {
  2. Utils.validateServiceInterface(service);
  3. if (validateEagerly) {
  4. //Pre-analysis method
  5. eagerlyValidateMethods(service);
  6. }
  7. return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
  8. new InvocationHandler() {
  9. private final Platform platform = Platform.get();
  10.  
  11. @Override public Object invoke(Object proxy, Method method, Object... args)
  12. throws Throwable {
  13. // If the method is a method from Object then defer to normal invocation. If it is a method in Object, call it directly
  14. if (method.getDeclaringClass() == Object.class) {
  15. return method.invoke(this, args);
  16. }
  17. //To be compatible with the Java 8 platform, this will not be executed in Android
  18. if (platform.isDefaultMethod(method)) {
  19. return platform.invokeDefaultMethod(method, service, proxy, args);
  20. }
  21. //The following is the key point, parsing method
  22. ServiceMethod serviceMethod = loadServiceMethod(method);
  23. OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
  24. return serviceMethod.callAdapter.adapt(okHttpCall);
  25. }
  26. });

The create method accepts a Class object, which is the interface we wrote, which contains the method for requesting the network marked by annotations. Note the return statement part, where the Proxy.newProxyInstance method is called. This is very important because the dynamic proxy mode is used. For more information about the dynamic proxy mode, you can refer to this article: http://www.codekk.com/blogs/d... A simple description is that Proxy.newProxyInstance generates an instance A, which is the proxy class, based on the Class object passed in. Whenever this proxy class A executes a method, it always calls the invoke method of InvocationHandler (the third parameter in Proxy.newProxyInstance). In this method, you can perform some operations (here, the annotation parameters of the parsing method, etc.), and through this method, the network request in the interface we wrote is actually executed.

Method resolution and type conversion

Let's take a closer look at the lines in invoke that parse the network request method. First, ServiceMethod serviceMethod = loadServiceMethod(method);, where the loadServiceMethod code is as follows:

  1. ServiceMethod loadServiceMethod(Method method) {
  2. ServiceMethod result;
  3. synchronized (serviceMethodCache) {
  4. result = serviceMethodCache.get(method);
  5. if (result == null ) {
  6. result = new ServiceMethod.Builder(this, method).build();
  7. serviceMethodCache.put(method, result);
  8. }
  9. }
  10. return result;
  11. }

It can be seen that the cache is searched first, and no further creation is done in the cache. A ServiceMethod object is created here. ServiceMethod is used to convert the call of the interface method into an HTTP request. In fact, in ServiceMethod, the annotations and parameters of the method in the interface are parsed, and it also has a toRequest method to generate a Request object. This Request object is the Request in OkHttp, which represents a network request (Retrofit actually entrusts the actual network request operation to OkHttp for execution). The following is part of the code to create ServiceMethod:

  1. public ServiceMethod build() {
  2. //Get callAdapter
  3. callAdapter = createCallAdapter();
  4. responseType = callAdapter.responseType();
  5. if (responseType == Response.class || responseType == okhttp3.Response.class) {
  6. throw methodError( "'"  
  7. + Utils.getRawType(responseType).getName()
  8. + "' is not a valid response body type. Did you mean ResponseBody?" );
  9. }
  10. //Get responseConverter
  11. responseConverter = createResponseConverter();
  12.  
  13. for (Annotation annotation : methodAnnotations) {
  14. //Analysis annotation
  15. parseMethodAnnotation(annotation);
  16. //Some code omitted
  17. ...
  18. }
  19. }

After getting the ServiceMethod object, pass it and the relevant parameters of the method call to the OkHttpCall object, which is this line of code: OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);. The following introduces OkHttpCall, which inherits from the Call interface. Call is the basic interface of Retrofit, representing sending network requests and response calls. It contains the following interface methods:

  • Response<T> execute() throws IOException; //Synchronous execution request
  • void enqueue(Callback<T> callback); //Asynchronous execution request, callback is used for callback
  • boolean isExecuted(); //Has it been executed?
  • void cancel(); //Cancel request
  • boolean isCanceled(); //Is it canceled?
  • Call<T> clone(); //Clone a request
  • Request request(); //Get the original request

OkHttpCall is an implementation class of Call, which encapsulates the native Call in OkHttp. Methods such as execute and enqueue are implemented in this class, which actually calls the corresponding methods of the native Call in OkHttp.

Next, pass OkHttpCall to the serviceMethod.callAdapter object. What is callAdapter here? In the code that creates ServiceMethod above, there is a line of code: callAdapter = createCallAdapter(). Here, calladapter is created. Inside this code, the corresponding CallAdapter is found based on the return type and annotation of the method. Where to find it? Find it in the adapterFactories collection of the Retrofit object. When we create Retrofit, we can call addCallAdapter to add CallAdapter to adapterFactories. In the previous basic usage, we did not add any CallAdapter, but an ExecutorCallAdapterFactory will be added to adapterFactories by default. Calling its get method can get the CallAdapter object.

So what does CallAdapter do? The adapt method is called above, which is to convert a Call to another type. For example, when Retrofit is used in combination with RxJava, the method in the interface can return Observable<T>, which is equivalent to the adapter pattern. By default, a Call object is obtained, which is ExecutorCallbackCall. The code is as follows:

  1. public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
  2. if (getRawType(returnType) != Call.class) {
  3. return   null ;
  4. }
  5. final Type responseType = Utils.getCallResponseType(returnType);
  6. return new CallAdapter<Call<?>>() {
  7. @Override public Type responseType() {
  8. return responseType;
  9. }
  10.  
  11. @Override public <R> Call<R> adapt(Call<R> call) {
  12. return new ExecutorCallbackCall<>(callbackExecutor, call);
  13. }
  14. };
  15. }

This ExecutorCallbackCall accepts a callbackExecutor (MainThreadExecutor by default in Android, which passes the returned data back to the main thread) and a call, which is OkhttpCall. Look at the ExecutorCallbackCall code:

  1. static final class ExecutorCallbackCall<T> implements Call<T> {
  2. final Executor callbackExecutor;
  3. final Call<T> delegate;
  4.  
  5. ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
  6. this.callbackExecutor = callbackExecutor;
  7. this.delegate = delegate;
  8. }
  9.  
  10. @Override public void enqueue(final Callback<T> callback) {
  11. if (callback == null ) throw new NullPointerException( "callback == null" );
  12.  
  13. delegate.enqueue(new Callback<T>() {
  14. @Override public void onResponse(Call<T> call, final Response<T> response) {
  15. callbackExecutor. execute (new Runnable() {
  16. @Override public void run() {
  17. if (delegate.isCanceled()) {
  18. // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
  19. callback.onFailure(ExecutorCallbackCall.this, new IOException( "Canceled" ));
  20. } else {
  21. callback.onResponse(ExecutorCallbackCall.this, response);
  22. }
  23. }
  24. });
  25. }
  26.  
  27. @Override public void onFailure(Call<T> call, final Throwable t) {
  28. callbackExecutor. execute (new Runnable() {
  29. @Override public void run() {
  30. callback.onFailure(ExecutorCallbackCall.this, t);
  31. }
  32. });
  33. }
  34. });
  35. }

In the enqueue method, the enqueue of OkHttpCall is called, so this is equivalent to the static proxy mode. The enqueue in OkHttpCall actually calls the enqueue in the native OkHttp, where the network request is actually issued. Some of the code is as follows:

  1. @Override public void enqueue(final Callback<T> callback) {
  2. if (callback == null ) throw new NullPointerException( "callback == null" );
  3. //The actual call to the network
  4. okhttp3.Call call;
  5. Throwable failure;
  6.  
  7. synchronized (this) {
  8. if (executed) throw new IllegalStateException( "Already executed." );
  9. executed = true ;
  10. //Some code is omitted
  11. ...
  12. call = rawCall;
  13. //enqueue asynchronous execution
  14. call.enqueue(new okhttp3.Callback() {
  15. @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
  16. throws IOException {
  17. Response<T> response;
  18. try {
  19. // Parsing data will use converterFactory to convert the response to the corresponding Java type
  20. response = parseResponse(rawResponse);
  21. } catch (Throwable e) {
  22. callFailure(e);
  23. return ;
  24. }
  25. callSuccess(response);
  26. }
  27.  
  28. @Override public void onFailure(okhttp3.Call call, IOException e) {
  29. try {
  30. callback.onFailure(OkHttpCall.this, e);
  31. } catch (Throwable t) {
  32. t.printStackTrace();
  33. }
  34. }
  35.  
  36. private void callFailure(Throwable e) {
  37. try {
  38. callback.onFailure(OkHttpCall.this, e);
  39. } catch (Throwable t) {
  40. t.printStackTrace();
  41. }
  42. }
  43.  
  44. private void callSuccess(Response<T> response) {
  45. try {
  46. callback.onResponse(OkHttpCall.this, response);
  47. } catch (Throwable t) {
  48. t.printStackTrace();
  49. }
  50. }
  51. });
  52. }

After OkHttp obtains the data, it parses the data and calls back the callback response method, and a network request is completed.

Summarize

The code of the entire Retrofit framework is not too long and is relatively easy to read. It mainly uses dynamic proxy to parse the Java interface into the corresponding network request, and then sends it to OkHttp for execution. It can also adapt to different CallAdapters and can be easily used in combination with RxJava.

<<:  Let’s take a look at BAT’s layout in the AR field. How would you rate it?

>>:  As tech giants race to seize the VR virtual reality market, has Google taken the lead?

Recommend

What should we pay attention to when placing the Wenchang Tower?

1. Introduction to Wenchang Tower Wenchang Pagoda...

Microsoft copied more OS X gestures in Windows 10

According to a report by The Verge, a Windows 10 ...

APP promotion and operation: It’s easy to reach 1 million users within 1 year!

Introduction: Whether it is a large company or a ...

Which one makes you fatter, rice dumplings or watermelons?

The Dragon Boat Festival is coming soon, and many...

Gaopengquan paid knowledge micro-course platform construction project

Gaopengquan knowledge paid micro-course platform ...

What can Foshan enterprises do by developing mini programs?

Mini Programs are a new way to attract new users....

Nexus and compact cameras couldn't save HTC from rapid decline

HTC released a number of new products in New York...