preface

To be a good Android developer, you need a complete set ofThe knowledge systemHere, let us grow together into what we think ~.

In the previous article, we analyzed OKHttp’s core source code in detail. If you don’t know OKHttp’s internal mechanism, you can take a look at the Android mainstream tripartite library source code analysis (a deep understanding of OKHttp source code). This article will take an in-depth look at the source code flow for Retrofit, currently the best network encapsulation framework for Android.

1. Basic usage process

1. Define an HTTP API for describing requests

public interface GitHubService {

     @GET("users/{user}/repos")
     Call<List<Repo>> listRepos(@Path("user") String user);
}
Copy the code

Create Retrofit and generate an implementation of the API (note: the annotation above the method represents the interface portion of the request, the return type is the return value type of the request, and the parameters of the method are the parameters of the request).

Retrofit = new retrofit.builder ().baseurl ("https://api.github.com/").build(); GitHubService service = retrofit.create(githubservice.class);Copy the code

3. Call API methods, generate Call, and execute the request

Call<List<Repo>> repos = service.listRepos("octocat"); repos.execute() or repos.enqueue()Copy the code

The basic use process of Retrofit is simple, but simplicity does not mean simplicity. To achieve this simple use process, Retrofit uses excellent architectural design and a large number of design patterns internally. After analyzing the latest version of Retrofit’s source code and a number of excellent Retrofit source code analysis articles, I found that To truly understand Retrofit’s core source code process and design philosophy, it’s important to first understand the nine design patterns, as follows:

1.Retrofit Build process Builder mode, Factory method mode 2. Create network request interface instance Process facade, proxy, singleton, Policy, Decorator (Builder) 3. Generate and execute the request process adapter pattern (broker pattern, decorator pattern)Copy the code

Secondly, you need to have a certain understanding of OKHttp source code, if you do not understand you can look at this Android mainstream tripartite library source code analysis (a deep understanding of OKHttp source code). Finally, let’s take a look inside Retrofit’s source code and see the beauty it brings to our design.

Ii. Retrofit construction process

1. Retrofit core object parsing

One global variable that is key to Retrofit is the LinkedHashMap() used in pre-V2.5 versions, which is a network request configuration object that is parsed from method annotations in the Network request interface.

Public final class Retrofit {// Network request configuration object, storage network request related configuration, For example, network request methods, data converters, network request adapters, network request factories, and base addresses. >> serviceMethodCache = new ConcurrentHashMap<>();Copy the code

Retrofit uses the Builder pattern to create an instance of Retrofit from the inner class Builder class as follows:

Public static final Class Builder {// Platform -> Android private final Platform Platform; Private @nullable okHttp3.call. Factory callFactory; Private @nullable HttpUrl baseUrl; private @nullable HttpUrl baseUrl; // Private final List<Converter.Factory> converterFactories = new ArrayList<>(); // Set of network request adapter factories, The default is ExecutorCallAdapterFactory private final List < CallAdapter. Factory > callAdapterFactories = new ArrayList < > (); // The callback method executor, which on Android by default encapsulates the MainThreadExecutor handler, defaults to: Private @nullable Executor callbackExecutor; // A switch that caches created ServiceMethod private Boolean validatembit/s;Copy the code

2. Internal construction of Builder

Let’s look at what the Builder internals do.

public static final class Builder { ... Builder(Platform platform) { this.platform = platform; } public Builder() { this(Platform.get()); }... } class Platform { private static final Platform PLATFORM = findPlatform(); static Platform get() { return PLATFORM; } private static Platform findPlatform() {try {Class.forname (" android.os.build "); if (Build.VERSION.SDK_INT ! = 0) { return new Android(); }} catch (ClassNotFoundException ignored) {} try {// Support the Java platform Class. return new Java8(); } catch (ClassNotFoundException ignored) { } return new Platform(); } static class Android extends Platform { ... @override public Executor defaultCallbackExecutor() {// Switch thread (child thread -> main thread) return MainThreadExecutor(); } // create the default network request adapter factory, on Android7.0 or Java8, // There are four CallAdapterFactory(policy mode) available in Retrofit: / / ExecutorCallAdapterFactory (default), GuavaCallAdapterFactory, / / va8CallAdapterFactory, RxJavaCallAdapterFactory @ Override List<? extends CallAdapter.Factory> defaultCallAdapterFactories( @Nullable Executor callbackExecutor) { if (callbackExecutor ==  null) throw new AssertionError(); ExecutorCallAdapterFactory executorFactory = new ExecutorCallAdapterFactory(callbackExecutor); return Build.VERSION.SDK_INT >= 24 ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory) : singletonList(executorFactory); }... @Override List<? extends Converter.Factory> defaultConverterFactories() { return Build.VERSION.SDK_INT >= 24 ? singletonList(OptionalConverterFactory.INSTANCE) : Collections.<Converter.Factory>emptyList(); }... Static class MainThreadExecutor implements Executor {private final Handler Handler = new Handler(Looper.getMainLooper()); @override public void execute(Runnable r) {handler.post(r); }}}Copy the code

As you can see, the default Platform, callAdapterFactories, and callbackExecutor were set up when the Builder was built inside.

3. Add baseUrl

To convert a String URL to an HttpUrl of OkHttp:

/** * Set the API base URL. * * @see #baseUrl(HttpUrl) */ public Builder baseUrl(String baseUrl) { checkNotNull(baseUrl,  "baseUrl == null"); return baseUrl(HttpUrl.get(baseUrl)); } public Builder baseUrl(HttpUrl baseUrl) { checkNotNull(baseUrl, "baseUrl == null"); List<String> pathSegments = baseUrl.pathSegments(); if (!" ".equals(pathSegments.get(pathSegments.size() - 1))) { throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl); } this.baseUrl = baseUrl; return this; }Copy the code

4. Add GsonConverterFactory

First of all, see GsonConverterFactory. Creat () of the source code.

public final class GsonConverterFactory extends Converter.Factory { public static GsonConverterFactory create() { return  create(new Gson()); } public static GsonConverterFactory create(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); return new GsonConverterFactory(gson); } private final Gson gson; Private GsonConverterFactory(Gson Gson) {this.gson = Gson; }Copy the code

Then, look inside the addConverterFactory() method.

public Builder addConverterFactory(Converter.Factory factory) {
    converterFactories.add(checkNotNull(factory, "factory null"));
    return this;
}
Copy the code

In this step, you put a GsonConverterFactory with an instance of a Gson object into the converterFactories.

5. Build process

public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } okhttp3.Call.Factory callFactory = this.callFactory; If (callFactory == null) {// Default okhttp callFactory = new OkHttpClient(); } Executor callbackExecutor = this.callbackExecutor; If (callbackExecutor = = null) {/ / Android default callbackExecutor callbackExecutor = platform. DefaultCallbackExecutor (); } // Make a defensive copy of the adapters and add the defaultCall adapter. List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories); / / add default adapter factory callAdapterFactories. At the end of the collection addAll (platform. DefaultCallAdapterFactorisca llbackExecutor)); // Make a defensive copy of the converters. List<Converter.Factory> converterFactories = new ArrayList<>( 1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize()); // Add the built-in converter factory first. This prevents overriding its behavior but also // ensures correct behavior when using converters thatconsumeall types. converterFactories.add(new BuiltInConverters()); converterFactories.addAll(this.converterFactories); converterFactories.addAll(platform.defaultConverterFactories(); return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories), unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly); }Copy the code

As you can see, eventually the six core objects we saw in the Builder class have all been configured into Retrofit objects.

Process of creating network request interface instance

Retrofit.create () uses the facade and proxy patterns to create interface instances for network requests. Let’s examine the create method.

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
        // 判断是否需要提前缓存ServiceMethod对象
        eagerlyValidateMethods(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 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);
            }
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
    });
 }

private void eagerlyValidateMethods(Class<?> service) {

  Platform platform = Platform.get();
  for (Method method : service.getDeclaredMethods()) {
    if (!platform.isDefaultMethod(method)) {
      loadServiceMethod(method);
    }
  }
}
Copy the code

Continue to look at the internal flow of loadServiceMethod

ServiceMethod<? > loadServiceMethod(Method method) { ServiceMethod<? > result = serviceMethodCache.get(method); if (result ! = null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); If (result = = null) {/ / parsing annotation configuration got ServiceMethod result = ServiceMethod. ParseAnnotations (this, method); Servicemethodcache. put(method, result); } } return result; } abstract class ServiceMethod<T> { static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, {// Parse annotation configuration via RequestFactory (factory mode, internal use of builder mode) 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."); } / / in the end is the method HttpServiceMethod build request return HttpServiceMethod. ParseAnnotations (retrofit, method, requestFactory); } abstract T invoke(Object[] args); }Copy the code

Here is the request construction core flow

The RequestFactory#Builder constructor and the parseAnnotations method are used to parse annotation configuration.

Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; This. MethodAnnotations = method.getannotations (); / / access network request parameter types in the interface methods enclosing parameterTypes = method. The getGenericParameterTypes (); / / the annotation in the request for network interface method content enclosing parameterAnnotationsArray = method. The getParameterAnnotations (); }Copy the code

Then see HttpServiceMethod. ParseAnnotations internal process ().

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { //1. Based on the return value and annotation type of the network request interface method, CallAdapter<ResponseT, ReturnT> CallAdapter = createCallAdapter(Retrofit,method); ResponseType = calladapter.responseType (); . //2. Get the corresponding data Converter from Retrofit based on the return value and annotation type of the network request interface method. ResponseT>responseConverter = createResponseConverter(retrofit,method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory; return newHttpServiceMethod<>(requestFactory, callFactory, callAdapter,responseConverter); }Copy the code
1.createCallAdapter(retrofit, method)
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter( Retrofit retrofit, Method Method) {/ / request access network interface Method return value types in the Type returnType = Method. The getGenericReturnType (); Annotations [] annotations = method.getannotations (); try { //noinspection unchecked return (CallAdapter<ResponseT, ReturnT>) 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); } } public CallAdapter<? ,? > callAdapter(Type returnType, Annotation[] annotations) { return nextCallAdapter(null, returnType, annotations); } public CallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) { ... int start = callAdapterFactories.indexOf(skipPast) + 1; / / traverse CallAdapter. Factory set to find suitable Factory for (int I = start, count = callAdapterFactories. The size (); i <count; i++) { CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations, this); if (adapter ! = null) { return adapter; }}}Copy the code
2.createResponseConverter(Retrofit retrofit, Method method, Type responseType)
private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter( Retrofit retrofit, Method method, Type responseType) { Annotation[] annotations = method.getAnnotations(); try { return retrofit.responseBodyConverter(responseType,annotations); } catch (RuntimeException e) { // Wide exception range because factories are user code. throw methodError(method, e, "Unable to create converter for%s", responseType); } } public <T> Converter<ResponseBody, T> responseBodyConverter(Type type, Annotation[] annotations) { return nextResponseBodyConverter(null, type, annotations); } public <T> Converter<ResponseBody, T> nextResponseBodyConverter( @Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) { ... int start = converterFactories.indexOf(skipPast) + 1; / / traverse the Converter. The Factory together and find the right Factory, here is the GsonResponseBodyConverter for (int I = start, count = converterFactories. The size (); i < count; i++) { Converter<ResponseBody, ? > converter = converterFactories.get(i).responseBodyConverter(type, annotations, this); if (converter ! = null) { //noinspection unchecked return (Converter<ResponseBody, T>) converter; }}Copy the code

Finally, the Invoke method of HttpServiceMethod is executed

@Override ReturnT invoke(Object[] args) {
    return callAdapter.adapt(
        new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
Copy the code

The result is an ExecutorCallbackCall object created in Adapt, which is a decorator, and OkHttpCall, which actually performs network requests.

Create a network request interface class instance and execute the request process

1, the service. ListRepos ()

1, Call<List<Repo>> repos = service.listRepos("octocat");Copy the code

The Service object is the dynamic Proxy object proxy.newProxyInstance (), which is intercepted when getCall() is called and then invokes its own InvocationHandler#invoke() to get the final Call object.

Repos.execute ()

@Override public Response<T> execute() throws IOException { okhttp3.Call call; synchronized (this) { if (executed) throw new IllegalStateException("Already executed."); executed = true; if (creationFailure ! = null) { if (creationFailure instanceof IOException) { throw (IOException) creationFailure; } else if (creationFailure instanceof RuntimeException) { throw (RuntimeException) creationFailure; } else { throw (Error) creationFailure; } } call = rawCall; If (call == null) {try {// create an OkHttp Request object call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); // Do not assign a fatal error to creationFailure. creationFailure = e; throw e; } } } if (canceled) { call.cancel(); } // Call OkHttpCall's execute() to send the network request (synchronization), and parse the data returned by the network request. Return parseResponse(call.execute()); } private okHttp3. Call createRawCall() throws IOException {// Create an okHttp3. Request okHttp3. Call Call = callFactory.newCall(requestFactory.create(args)); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call; } Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); // Remove the body's source (the only stateful object) so we can pass the response along. rawResponse = rawResponse.newBuilder() .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) .build(); 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); } ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody); Try {/ / response body into a Java object T body = responseConverter. Convert (catchingBody); return Response.success(body, rawResponse); } catch (RuntimeException e) { // If the underlying source threw an exception, propagate that rather than indicating it was // a runtime exception. catchingBody.throwIfCaught(); throw e; }}Copy the code

3, asynchronous request flow reponse.enqueque

Public void enqueue(final Callback<T>(); public void enqueue(final Callback<T>()); {@override public void onResponse(Call<T> Call, finalResponse<T>response) { Execute (new Runnable() {@override public void run() {if (kinete.iscanceled ()) {callBackExecutor.execute (new Runnable() {@override public void run() {if (kinete.iscanceled ()) { callback.onFailure(ExecutorCallbackCall.this, newIOException("Canceled")); } else { callback.onResponse(ExecutorCallbackCall.this,respons); }}}); } @Override public void onFailure(Call<T> call, final Throwable t) { callbackExecutor.execute(new Runnable() { @Override public void run() { callback.onFailure(ExecutorCallbackCall.this, t); }}); }}); }Copy the code

Take a look at the delegate.enqueue internal process.

@Override public void enqueue(final Callback<T> callback) { 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 {// create an OkHttp Request object and encapsulate it as OkHttp. Call // Call = rawCall = createRawCall(); } catch (Throwable t) { failure = creationFailure = t; } } @Override public void enqueue(final Callback<T> callback) { checkNotNull(callback, "callback == null"); okhttp3.Call call; Throwable failure; . 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) { t.printStackTrace(); } } @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) { t.printStackTrace(); }}}); }Copy the code

If you’re looking at this, congratulations, you’ve got a pretty good idea of Retrofit, but I recommend that you take the initiative to get to the bottom of Retrofit with the latest version of the source code so that you can see what it really is. Finally, attached is a source code flow chart of Stay’s Retrofit. Note that this is the process before V2.5, but after reading the source code analysis above, we know that the main process is unchanged.

Five, the summary

At its core, Retrofit is just a wrapper around a RESTful HTTP Web request framework. However, it encapsulates OkHttp internally with a number of design patterns that make it very simple and easy to understand for users. Internally, it mainly uses dynamic proxy to dynamically parse the annotation of network request interface into HTTP request, and finally execute the request process. Well, so far, our Android mainstream tripartite library source code analysis of the network library analysis part has been completed. Next, will bring you the most popular picture loading framework Glide source analysis, please look forward to ~

Reference links:

Retrofit V2.5.0 source code

2. Android advanced Light

Android: Take you through Retrofit 2.0 source code hand in hand

Retrofit analysis – beautiful decoupling routines


Thank you for reading this article and I hope you can share it with your friends or technical group, it means a lot to me.

I hope we can be friends inGithub,The Denver nuggetsTo share knowledge.