OkHttp4.2.2 source code parsing

Retrofit2.6.2 source code parsing


The previous article briefly analyzed the OkHttp4.2.2 source code, and attached the corresponding flow chart. Using OkHttp directly wasn’t convenient enough, so Retrofit was born. As of the date of this writing, the latest version of Retrofit is 2.6.2, which itself uses OkHttp3.14.4 and coroutines1.3.2. Although Retrofit does not use the latest version of OkHttp, it does not affect the previously analyzed process.

I. Flow chart

Two, simple use

  1. Add the dependent
implementation("Com. Squareup. Retrofit2: retrofit: 2.6.2." ")
Copy the code
  1. Define the request interface
interface ApiService {
    @GET("users/{user}/repos")
    suspend fun listRepos(
            @Path("user") user: String
    ): FoodResponse<List<Repo>>
}
Copy the code
  1. Build the Retrofit build Api instance
val baseUrl = "https://api.github.com/"

val okClient = OkHttpClient.Builder()
    .addInterceptor(loggingInterceptor)
    ......
    .build()

Retrofit.Builder()
    .baseUrl(baseUrl)
    .client(okClient) // There is default, not required
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    
val service = retrofit.create(ApiService::class.java)
Copy the code
  1. The initiating
//1. Callback mode
service.listRepos1("zx").enqueue(object : Callback<List<Repo>> {
    override fun onFailure(call: Call<List<Repo>>, t: Throwable){}override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo> >){}})//2. Coroutine
val repos = service.listRepos("zx")

/ / 3. RxJava etc
})
Copy the code

Three, source code analysis

I’ve shown you how to initiate a web request through Retrofit in the four steps above, and then I’ll look at the source code using the process above.

Building Retrofit

In building Retrofit we focused on three methods: the Builder method, the baseUrl() method, and the Build () method

  1. Builder
public Builder(a) {
    // Platform fetch, Platform we also see in OkHttp analysis
    this(Platform.get());
}
Copy the code
  1. baseUrl
public Builder baseUrl(String baseUrl) {
    Objects.requireNonNull(baseUrl, "baseUrl == null");
    return baseUrl(HttpUrl.get(baseUrl));
}

public Builder baseUrl(HttpUrl baseUrl) {
    Objects.requireNonNull(baseUrl, "baseUrl == null");
    List<String> pathSegments = baseUrl.pathSegments();
    // Check if baseUrl ends with/and throw an exception if it does not
    if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
    }
    this.baseUrl = baseUrl;
    return this;
}
Copy the code

So instead of saying what baseUrl does, if we look at the comment line, baseUrl does not end with a slash, it throws an exception.

Note that the baseUrl type is HttpUrl, so it’s not the same as the String we used. Httpurl.get handles our Settings, so we don’t necessarily get an exception if our String baseUrl doesn’t end in /.

  1. build
public Retrofit build(a) {
    if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
    }

    okhttp3.Call.Factory callFactory = this.callFactory;
    if (callFactory == null) {
        // If the callFactory is not set, OkHttpClient is used by default. Retrofit uses OkHttp by default
        //OkHttpClient implements the okHttp3.call.factory interface
        callFactory = new OkHttpClient();
    }

    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
        //callbackExecutor
        // If no callback executor is set, the platform default callback executor is used
        callbackExecutor = platform.defaultCallbackExecutor();
    }

    // Make a defensive copy of the adapters and add the default Call adapter.
    // Network request Adaptation Factory set = user set adaptation factory + platform default adaptation factory
    // The user sets the adaptation factory
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    // The platform ADAPTS the factory by default, notice that the callback executor is passed in here
    callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));

    // Make a defensive copy of the converters.
    // Set of data transformation factories = built-in transformation factories + user-set transformation factories + platform default transformation factories
    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 that consume all types.
    // Built-in conversion factory
    converterFactories.add(new BuiltInConverters());
    // The user sets the transformation factory
    converterFactories.addAll(this.converterFactories);
    // Platform default conversion factory
    converterFactories.addAll(platform.defaultConverterFactories());

    / / build Retrofit
    return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
            unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
Copy the code

The Build method mainly handles parameters (null values and platform defaults) and builds Retrofit instances.

The word Platform appears several times. What does this class do?

class Platform {
    private static final Platform PLATFORM = findPlatform();

    static Platform get() {
        returnPLATFORM; Private static Platform private static PlatformfindPlatform() {
        try {
            Class.forName("android.os.Build");
            if(Build.VERSION.SDK_INT ! = 0) {return new Android();
            }
        } catch (ClassNotFoundException ignored) {
        }
        try {
            Class.forName("java.util.Optional");
            return new Java8();
        } catch (ClassNotFoundException ignored) {
        }
        returnnew Platform(); }... @IgnoreJRERequirement // Only classloaded and used on Java 8. static class Java8 extends Platform { ...... } static class Android extends Platform { @IgnoreJRERequirement // Guarded by API check. @Override boolean isDefaultMethod(Method method) {if (Build.VERSION.SDK_INT < 24) {
                return false;
            }
            return method.isDefault();
        }

        @Override
        public Executor defaultCallbackExecutor() {// Returns the default callback executorreturn new MainThreadExecutor();
        }

        @Override
        List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
                @Nullable Executor callbackExecutor) {
            if(callbackExecutor == null) throw new AssertionError(); / / build the default factory, a note here to callback executives DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory (callbackExecutor); // Return the default adaptation factoryreturn Build.VERSION.SDK_INT >= 24
                    ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)
                    : singletonList(executorFactory);
        }

        @Override
        int defaultCallAdapterFactoriesSize() {
            returnBuild.VERSION.SDK_INT >= 24 ? 2:1; } @Override List<? extends Converter.Factory>defaultConverterFactories() {// Return the default conversion factoryreturn Build.VERSION.SDK_INT >= 24
                    ? singletonList(OptionalConverterFactory.INSTANCE)
                    : Collections.<Converter.Factory>emptyList();
        }

        @Override
        int defaultConverterFactoriesSize() {
            returnBuild.VERSION.SDK_INT >= 24 ? 1:0; } 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

Three points:

  • You can see from the findPlatform method that 2.6.2 supports Android and Java. (remember there was iOS in 2.0.1, and the latest GitHub code has removed Java8.)
  • The platform default callback executor (Handler main thread), default adaptation factory, and default transformation factory are set
  • Java8 and Android 24 is a cut-off point (specific can see CompletableFutureCallAdapterFactory and OptionalConverterFactory)

Creating an Api instance

The build process for Retrofit was analyzed above, followed by the section on creating an Api instance. Actually creating an Api instance seems simple, with just one line of code:

val service = retrofit.create(ApiService::class.java)
Copy the code

Is it really that simple? Read on:

public <T> T create(final Class<T> service) {
    // Check whether the Service Interface meets the requirements
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
        // Whether the Service Interface method needs to be verified in advance,
        // true: the method is added to the serviceMethodCache when create is called
        // false: Do processing and caching when invocationHandler. invoke is invoked
        // Both true and false end up calling loadServiceMethod
        eagerlyValidateMethods(service);
    }
    // Use the dynamic proxy to get all the annotation configuration of the request interface and create an instance of the network request interface
    return (T) Proxy.newProxyInstance(service.getClassLoader(), newClass<? >[]{service},new InvocationHandler() {
                ......
            });
}

// Verify the Service Interface method ahead of time
private void eagerlyValidateMethods(Class
        service) {
    Platform platform = Platform.get();
    for (Method method : service.getDeclaredMethods()) {
        if(! platform.isDefaultMethod(method) && ! Modifier.isStatic (method.getModifiers())) {IsDefaultMethod Indicates whether the platform default method is fasle returned in 2.6.2
            // isStatic whether to be modified by staticloadServiceMethod(method); }}}Copy the code

Sure enough, that looks easy. This step is not for further analysis of loadServiceMethod and its subsequent operations, but for the invoke invoke execution, as NORMALLY I have not set validatembit/s.

Making a Network request

public <T> T create(final Class<T> service) {...return (T) Proxy.newProxyInstance(service.getClassLoader(), newClass<? >[]{service},new InvocationHandler() {
                private final Platform platform = Platform.get();
                private final Object[] emptyArgs = new Object[0];

                // Execute the method in the instance of newProxyInstance
                @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.
                    // Object Default method
                    if (method.getDeclaringClass() == Object.class) {
                        return method.invoke(this, args);
                    }
                    // Platform default method
                    if (platform.isDefaultMethod(method)) {
                        return platform.invokeDefaultMethod(method, service, proxy, args);
                    }
                    // Load the Service Interface method and invoke to create an OkHttpCall
                    returnloadServiceMethod(method).invoke(args ! =null? args : emptyArgs); }}); }ConcurrentHashMap supports multithreaded access and is thread-safe
private finalMap<Method, ServiceMethod<? >> serviceMethodCache =newConcurrentHashMap<>(); ServiceMethod<? > loadServiceMethod(Method method) {// Get the cacheServiceMethod<? > result = serviceMethodCache.get(method);if(result ! =null) return result;

    synchronized (serviceMethodCache) {
        result = serviceMethodCache.get(method);
        if (result == null) {
            // Parse annotations to generate ServiceMethod
            result = ServiceMethod.parseAnnotations(this, method);
            // serviceMethodCache Adds to the cacheserviceMethodCache.put(method, result); }}return result;
}

Copy the code

Follow up ServiceMethod parseAnnotations

abstract class ServiceMethod<T> {
    static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
        // Parse the annotation parameters
        BaseUrl relativeUrl, method annotation parameter type parameter annotation, etcRequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); .// HttpServiceMethod is a subclass of follow-on ServiceMethod
        // Create a callAdapter and responseConverter in parseAnnotations
        return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
    }

    // Create an OkHttpCall to initiate a request from adapt
    abstract @Nullable
    T invoke(Object[] args);
}

Copy the code

To follow up HttpServiceMethod. ParseAnnotations look

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> 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) {
        // Is Kotlin Suspend method
        Type[] parameterTypes = method.getGenericParameterTypes();
        Type responseType = Utils.getParameterLowerBound(0,
                (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
        if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
            // Unwrap the actual body type from Response<T>.
            responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
            Response
      
            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);
        // Skip the callback executor annotation
        annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
        adapterType = method.getGenericReturnType();
    }
    
    // Create a CallAdapter from Retrofit, method, adapterType, Annotations
    CallAdapter<ResponseT, ReturnT> callAdapter =
            createCallAdapter(retrofit, method, adapterType, annotations);
    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<String>)");
    }
    // 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 Converter based on retrofit, method, adapterType
    Converter<ResponseBody, ResponseT> responseConverter =
            createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if(! isKotlinSuspendFunction) {// Non-Kotlin Suspend method
        return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
        // The Kotlin Suspend method is included in Response
        //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
                callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
        // The Kotlin Suspend method is not included in Response
        //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
        return (HttpServiceMethod<ResponseT, ReturnT>) newSuspendForBody<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); }}Copy the code

HttpServiceMethod parseAnnotations finally returned to the HttpServiceMethod, back to Retrofit. Execute invokede call the create method. HttpServiceMethod implements invoke as follows:

@Override final @Nullable ReturnT invoke(Object[] args) {
// Create a Call object
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    // Adapt is also an abstract method implemented in CallFranchise, SuspendForResponse, SuspendForBody
    return adapt(call, args);
}
Copy the code

The createCallAdapter and createResponseConverter methods are also important. Here is how to create a CallAdapter. CreateCallAdapter -> retrofit.callAdapter -> nextCallAdapter:

publicCallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType,
                                         Annotation[] annotations) {
    ......

    int start = callAdapterFactories.indexOf(skipPast) + 1;
    // Walk through the collection of callAdapterFactories built by Retrofit.build to find suitable adaptation factories
    for (inti = start, count = callAdapterFactories.size(); i < count; i++) { CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations,this);
        if(adapter ! =null) {
            returnadapter; }}...throw new IllegalArgumentException(builder.toString());
}
Copy the code

Finding the corresponding callAdapter is as simple as depending on the type (for example, if Call is returned by default and coroutine is decorated by suspend, see the CallAdapterFactory get method).

SuspendForBody implements HttpServiceMethod:

  static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT.Object> {
    private final CallAdapter<ResponseT, Call<ResponseT>> callAdapter;
    private final booleanisNullable; .@Override protected Object adapt(Call<ResponseT> call, Object[] args) {
      call = callAdapter.adapt(call);

      //noinspection unchecked Checked by reflection inside RequestFactory.
      Continuation<ResponseT> continuation = (Continuation<ResponseT>) args[args.length - 1];

      /** As inside await and awaitNullable, calls to OkHttp call.enqueue () can sometimes invoke the provided callback before the exception returns, and Call stack frames can return. Coroutines intercept subsequent calls to continuations and throw exceptions synchronously. If the checked exception is not declared in the interface method, the Java proxy cannot raise the checked exception. In order to avoid abnormal will check the packing in UndeclaredThrowableException to intercept it and offered to help the program, the helper will enforce suspend operation, so that you can pass it to continue to operate to bypass this limitation. * * /
      try {
        // Call the specific upvote method
        return isNullable
            ? KotlinExtensions.awaitNullable(call, continuation)
            : KotlinExtensions.await(call, continuation);
      } catch (Exception e) {
        returnKotlinExtensions.yieldAndThrow(e, continuation); }}}Copy the code

The next implementation of the extension method await is as follows:

suspend fun <T : Any> Call<T>.await(): T {
  return suspendCancellableCoroutine {continuation - > continuation. InvokeOnCancellation {the cancel ()} / / actual is call call the enqueue enqueue(object : Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) {if (response.isSuccessful) {
          val body = response.body()
          if (body == null) {
            val invocation = call.request().tag(Invocation::class.java)!!
            val method = invocation.method()
            val e = KotlinNullPointerException("Response from " +
                method.declaringClass.name +
                '. ' +
                method.name +
                " was null but response body type was declared as non-null")
            continuation.resumeWithException(e)
          } elseResume (body)}}else {
          continuation.resumeWithException(HttpException(response))
        }
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        continuation.resumeWithException(t)
      }
    })
  }
}
Copy the code

When performing the KotlinExtensions. Await/awaitNullable, executes the corresponding extension function. It is not hard to see that the Call. Enqueue is used to initiate the request, and the corresponding result is resumed by the corresponding resume function. This Call object is the OkHttpCall from invoke above, and it is created as follows. It is not hard to guess that the final originating request and data transformation are in it, so the enqueue method called in the above await extension is also implemented in it.

// Avoid rolling back to see OkHttpCall, Call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);Copy the code

Take a look at the enqueue method of OkHttpCall

@Override public void enqueue(final Callback<T> callback) {
    checkNotNull(callback, "callback == null"); .// After a series of judgment operations, the request is finally initiated
    call.enqueue(new okhttp3.Callback() {
        @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
                / / parse the 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}}... }); }Parse the response data
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); . ExceptionCatchingResponseBody catchingBody =new ExceptionCatchingResponseBody(rawBody);
    try {
        //responseConverter finally parses data through Converter
        T body = responseConverter.convert(catchingBody);
        return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
        ......
    }
    
Copy the code

Four,

Retrofit simplifies the configuration of network requests through interface annotations and uses dynamic proxy loading to parse annotation execution. The OkHttpCall is then used to initiate the request, and the response is converted to The CallAdapter and the final data is handed to the developer.

Using Retrofit and the source code, Retrofit does encapsulate OkHttp and makes extensive use of annotations and design patterns such as builder pattern, dynamic proxy pattern, factory pattern, adapter pattern, and so on.

Although this is based on 2.6.2 parsing, there are some changes in the latest commit, as mentioned earlier.

Five, the complement

thread

I didn’t see how to switch threads because I was using coroutines above, so now I’m going to see 1 see, I’m going to add comments. As follows:

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  private final @Nullable Executor callbackExecutor;

  // The argument passed in is the callback executor, remember where it was passed in?
  // Retrofit builds based on the relevant platform
  DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  // This method is used for createCallAdapter
  @Override public @NullableCallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit) {if(getRawType(returnType) ! = Call.class) {return null;
    }
    if(! (returnTypeinstanceof ParameterizedType)) {
      throw new IllegalArgumentException(
          "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
    }
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

    // Get executor (coroutines not required)
    final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
        ? null
        : callbackExecutor;

    return newCallAdapter<Object, Call<? > > () {@Override public Type responseType(a) {
        return responseType;
      }

      @Override public Call<Object> adapt(Call<Object> call) {
        // Returns call, which is non-coroutine with the ExecutorCallbackCall package layer
        return executor == null
            ? call
            : newExecutorCallbackCall<>(executor, call); }}; }static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override public void enqueue(final Callback<T> callback) {
      checkNotNull(callback, "callback == null");
    
      // The delegate here is OkHttpCall
      delegate.enqueue(new Callback<T>() {
        @Override public void onResponse(Call<T> call, final Response<T> response) {
          // The callback executor completes the thread switch
          callbackExecutor.execute(new Runnable() {
            @Override public void run(a) {
              if (delegate.isCanceled()) {
                // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation.
                callback.onFailure(ExecutorCallbackCall.this.new IOException("Canceled"));
              } else {
                // The result is called back via callback
                callback.onResponse(ExecutorCallbackCall.this, response); }}}); }@Override public void onFailure(Call<T> call, final Throwable t) {
          callbackExecutor.execute(new Runnable() {
            @Override public void run(a) {
              callback.onFailure(ExecutorCallbackCall.this, t); }}); }}); }@Override public boolean isExecuted(a) {
      return delegate.isExecuted();
    }

    @Override public Response<T> execute(a) throws IOException {
      return delegate.execute();
    }

    @Override public void cancel(a) {
      delegate.cancel();
    }

    @Override public boolean isCanceled(a) {
      return delegate.isCanceled();
    }

    @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
    @Override public Call<T> clone(a) {
      return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
    }

    @Override public Request request(a) {
      returndelegate.request(); }}}Copy the code