preface

Those of you who have seen Retrofit know that one of Retrofit’s core technologies is annotations + reflection + dynamic proxy. This article will take a look at the implementation of Retrofit’s framework, combined with the previous article on generics and reflection, and take a look at how it handles collecting parameters, assembling requests, and the entire process.

To learn more about annotations and generic erasing and reflection to get generic types, check out the general principles of generics

directory

I. Agent mode

Specific analysis to see the agent mode

Static proxy mode (normal, mandatory) typically has three roles

Abstract roles: Public methods provided by proxy and real roles, typically interfaces

Real roles: Interfaces that implement abstract roles and internally implement real business logic

Proxy roles: Implements an interface for abstract roles, proxies real roles, and then appends its own operations to centrally manage the processing of real roles

When using the proxy mode, we should know the purpose of the proxy mode, that is, the benefits of the proxy mode, to avoid external direct access to the real object, only through the proxy object access, indirect access to the target object, to prevent some unnecessary complexity, unified scheduling of real objects in the proxy mode. Real objects only care about their own business logic.

If there is A scenario in the project that implements image loading, how would you implement it considering the substitutability of the current Glid loading image, that is, it may be replaced by other better technology A later on? It is also possible that the project is constantly evolving and the technology is constantly updated.

Then directly set up the specific object, using the interface object, such as the early componentized communication will use this method.

However, static proxy also has disadvantages. In the process of business development, each service function corresponds to one interface, and each proxy class can only realize one interface. In this way, there are too many proxy class objects written, so dynamic proxy needs to be considered

Dynamic proxy source code analysis can be seen in proxy mode, proxy mode generated classes exist in memory, we do not see related file generation. Of course, it’s not impossible to see what dynamic proxy generation looks like.

public interface ApiService { void get(); void post(); } public class TestProxy { public static void main(String[] args){ String name = ApiService.class.getName()+"$Proxy0"; / / the source code generated in the way of byte byte [] bytes = ProxyGenerator. GenerateProxyClass (name, new Class [] {ApiService. Class}); try { FileOutputStream fos = new FileOutputStream("src/" + name + ".class"); fos.write(bytes); fos.close(); }catch (Exception e){ } } }Copy the code

A demo.apiservice $proxy0.class file is generated with the following contents

// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package demo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class ApiService$Proxy0 extends Proxy implements ApiService { private static Method m1; private static Method m4; private static Method m2; private static Method m3; private static Method m0; public ApiService$Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void post() throws { try { super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void get() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m4 = Class.forName("demo.ApiService").getMethod("post"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("demo.ApiService").getMethod("get"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); }}}Copy the code

Super.h is the InvocationHandler passed using a dynamic proxy, as shown in the code above.

Second, the Retrofit

Annotation + reflection + dynamic proxy

Specific use of Retrofit using tutorial II

Retrofit is itself a factory from an architectural point of view. It collects and processes interface data, sends it to OKHttp for processing, and then wraps the returned data back to the user. It reduces code usage, code maintenance risks, improves our development efficiency, and can be adapted to Gson or RxJava for data transformation extensions.

From a technical point of view, the essence of Retrofit is reflection + dynamic proxy + annotations +Convert+CallAdapter. The extensibility of the latter two results in perfect access to RxJavaCallAdapter and custom Convert parse data. Convert is used to Convert data, including request parameter values and data returned by the server. The CallAdapter is used to assemble data, put it into the OKHttpCall, and convert it by its ADAPT to the data type we expect to return, which is the return type of the interface method. If the RxJavaCallAdapter is used, it will return an observed and notify the observer when there is data. Network requests are handled within adapt methods.

1. Annotation collection

Sample ApiService. Java

public interface ApiService {
    @GET("/record")
    <T> Call<BaseResponse<T>> get();

    @POST("/post")
    @FormUrlEncoded
    Call<BaseResponse> post(@Field("key") String value);
}
Copy the code

use

Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.xxx.com").build();
ApiService apiService=  retrofit.create(ApiService.class);
Copy the code

When we call the ApiService method, we go to the dynamic proxy invoke method from the create method we are analyzing so far

Analysis directly from Retrofit’s CREATE method

Note 1 and 2 check whether they are Object methods, Android or Java system methods to avoid wireless loop calls and stack overflow.

The loadServiceMethod in comment 3 is the key

ServiceMethod<? > loadServiceMethod(Method method) { ServiceMethod<? > result = serviceMethodCache.get(method); // Cache from Map if (result! = null) return result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); / / multi-threaded operation, another thread after the execution, here directly fetch the if (result = = null) {result = ServiceMethod. ParseAnnotations (this, method); serviceMethodCache.put(method, result); } } return result; }Copy the code

This method fetches the corresponding ServiceMethod object from the Map based on the Method method. If it doesn’t have one, it collects it using the parseAnnotations method

static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) { RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); / / comment 1 Type returnType = method. GetGenericReturnType (); / / 2 Method the return type of the 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); // comment 3}Copy the code

parseAnnotations  Note 1

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) { return new Builder(retrofit, method).build(); } Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); In the way that / / comment tag enclosing parameterTypes = method. The getGenericParameterTypes (); / / returns an array of Type objects, it said in statement order this Method the Method of object representation in the form of a parameter Type enclosing parameterAnnotationsArray = Method. The getParameterAnnotations (); Call (@field (" XXX ") @field ("xxx2") String key)}Copy the code

As you can see, this is mainly collecting methods, annotations on methods, parameter Type types, and all annotations in parameters.

Let’s look at the build method of its Builder again

RequestFactory build() { for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); For example, if @formurlencoded methods are used, @multipart cannot be coded. If (httpMethod == null) {throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.)."); } if (! hasBody) { if (isMultipart) { throw methodError(method, "Multipart can only be specified on HTTP methods with request body (e.g., @POST)."); } if (isFormEncoded) { throw methodError(method, "FormUrlEncoded can only be specified on HTTP methods with " + "request body (e.g., @POST)."); } } int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<? >[parameterCount]; for (int p = 0; p < parameterCount; p++) { parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p]); RelativeUrl == null &&! gotUrl) { throw methodError(method, "Missing either @%s URL or @Url parameter.", httpMethod); } if (! isFormEncoded && ! isMultipart && ! hasBody && gotBody) { throw methodError(method, "Non-body HTTP method cannot contain @Body."); } if (isFormEncoded && ! gotField) { throw methodError(method, "Form-encoded method must contain at least one @Field."); } if (isMultipart && ! gotPart) { throw methodError(method, "Multipart method must contain at least one @Part."); } returnCopy the code

This is to check whether annotations are used in accordance with Retrofit rules, such as not allowing multiple @URL annotation tags in parameters, as shown in the parseParameter method. All results are then collected to ParameterHandlers
[] array is equivalent to a ParameterHandler that holds the value of an annotation, such as the key of @field (“key”). All of this is in the RequestFactory object.

In addition to the rule validation and collecting the ParameterHandlers class, the other important one is convert. In comment 2, in the parseParameter function, there is something like this, and in comment 1, it gets convert from Retrofit. Stored to ParameterHandler, the corresponding convert method is later called during request initiation to assemble the data.

if (annotation instanceof Query) { validateResolvableType(p, type); Query query = (Query) annotation; String name = query.value(); boolean encoded = query.encoded(); Class<? > rawParameterType = Utils.getRawType(type); gotQuery = true; if (Iterable.class.isAssignableFrom(rawParameterType)) { if (! (type instanceof ParameterizedType)) { throw parameterError(method, p, rawParameterType.getSimpleName() + " must include generic type (e.g., " + rawParameterType.getSimpleName() + "<String>)"); } ParameterizedType parameterizedType = (ParameterizedType) type; Type iterableType = Utils.getParameterUpperBound(0, parameterizedType); Converter<? , String> converter = retrofit.stringConverter(iterableType, annotations); Return new ParameterHandler.Query<>(name, converter, encoded).iterable(); }Copy the code

ParseAnnotations 2 getGenericReturnType Returns Type of the following Type

Method[] methods = ApiService.class.getDeclaredMethods(); Type type = methods[0].getGenericReturnType(); System.out.println(type); System.out.println(methods[0].getReturnType()); Output retrofit2. Call < com. Demo. Hehe. BaseResponse < T > > interface retrofit2. Call trueCopy the code

Annotations the parseAnnotations method checks whether the return type is written according to its requirements, such as returning void, which definitely doesn’t. There are many ways to write a return value, such as using generics or not, including arrays of generic types, for example, ApiService. Mainly through Utils hasUnresolveableType check, Check that each element that returns Type complies with the rule (TypeVariable, GenericArrayType, WildcardType, ParameterizedType).

Annotations 3 start parsing method annotations using parseAnnotations

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method); // Comment 1 is not parsed here. Then there are only two Retrofit the default CallAdapter - > CompletableFutureCallAdapterFactory and ExecutorCallAdapterFactory Type responseType = callAdapter.responseType(); if (responseType == Response.class || responseType == okhttp3.Response.class) { throw methodError(method, "'" + Utils.getRawType(responseType).getName() + "' is not a valid response body type. Did you mean ResponseBody?" ); } if (requestFactory.httpMethod.equals("HEAD") && ! Void.class.equals(responseType)) { throw methodError(method, "HEAD method must use Void as response type."); } Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); // Comment 2 okhttp3.call.factory callFactory = retrofit.callFactory; return new HttpServiceMethod<>(requestFactory, callFactory, callAdapter, responseConverter); }Copy the code

The Adapter at comment 1 is examined later

The Converter in note 2 will be analyzed later

We continue with the constructor of the return type HttpServiceMethod, which collects all the information

private HttpServiceMethod(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
      CallAdapter<ResponseT, ReturnT> callAdapter,
      Converter<ResponseBody, ResponseT> responseConverter) {
    this.requestFactory = requestFactory;
    this.callFactory = callFactory;
    this.callAdapter = callAdapter;
    this.responseConverter = responseConverter;
  }
Copy the code

The invoke method of HttpServiceMethod is invoked after loadServiceMethod is executed in the dynamic proxy, passing the parameters of the caller’s method

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

The parameter name in resquestFactory is mapped to the value in ARGS via OkHttpCall, and concatenated after the Url if it is a GET request. At this point, all parameters have been collected. This OkHttpCall is Retrofit, not OkHttp. The CallAdapter wraps the OkHttpCall and returns the OkHttpCall object, which then enters the OkHttp request phase.

Next, you just need to look at the CallAdapter adapt method to see what’s going on internally.

2, CallAdapter

So if YOU look at HttpServiceMethod and you collect annotations that’s where you get the CallAdapter

CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method);
Copy the code

Android side, compatible with Java 8+, Android API24 or more, there will be two CallAdapter, Is CompletableFutureCallAdapterFactory and ExecutorCallAdapterFactory respectively

The default type that the CallAdapter factory added when Retrofit was created

@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); }Copy the code

createCallAdapter

HttpServiceMethod.java private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter( Retrofit retrofit, Method method) { Type returnType = method.getGenericReturnType(); Annotation[] 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); } } Retrofit.java public CallAdapter<? ,? > callAdapter(Type returnType, Annotation[] annotations) { return nextCallAdapter(null, returnType, annotations); } public CallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) { checkNotNull(returnType, "returnType == null"); checkNotNull(annotations, "annotations == null"); int start = callAdapterFactories.indexOf(skipPast) + 1; for (int i = start, count = callAdapterFactories.size(); i < count; i++) { CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations, this); If (adapter! = null) { return adapter; }}... throw new IllegalArgumentException(builder.toString()); }Copy the code

The key code inside the method to get a CallAdapter is still in the GET method within each CallAdapter.

CompletableFutureCallAdapterFactory

@Override public @Nullable CallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit) { if (getRawType(returnType) ! = completableFuture.class) {// Return type such as A<> A is CompleteFuture return null; } if (! (returnType instanceof ParameterizedType)) {// whether the returnType is A<> throw new IllegalStateException("CompletableFuture return type must be parameterized" + " as CompletableFuture<Foo> or CompletableFuture<? extends Foo>"); } Type innerType = getParameterUpperBound(0, (ParameterizedType) returnType); //innerType Returns the upper bound of the type if (getRawType(innerType)! = response.class) {return type is Response, i.e. Extend Response such as // Generic type is not Response<T>. Use it for body-only adapter. return new BodyCallAdapter<>(innerType); } // Generic type is Response<T>. Extract T and create the Response version of the adapter. if (! (innerType instanceof ParameterizedType)) { throw new IllegalStateException("Response must be parameterized" + " as Response<Foo> or Response<? extends Foo>"); } Type responseType = getParameterUpperBound(0, (ParameterizedType) innerType); Return ResponseCallAdapter<>(responseType); }Copy the code

Unless the method defined by the ApiService interface returns a value of type CompletableFuture, the Future needs to be used to manipulate the result of the request. If you don’t know, you can use the Future first.

DefaultCallAdapterFactory

@Override public @Nullable CallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit) { if (getRawType(returnType) ! = Call.class) { return null; } final Type responseType = Utils.getCallResponseType(returnType); return new CallAdapter<Object, Call<? >>() { @Override public Type responseType() { return responseType; } @Override public Call<Object> adapt(Call<Object> call) { return new ExecutorCallbackCall<>(callbackExecutor, call); }}; }Copy the code

The adapt method returns the default ExecutorCallbackCall, uses the proxy mode internally, and indirectly calls Retrofit’s OkHttpCall. When the OkHttpCall is executed, It switches to the main thread via the CallbackExecutor(which internally is a main thread Handler).

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"); delegate.enqueue(new Callback<T>() { @Override public void onResponse(Call<T> call, final Response<T> response) { callbackExecutor.execute(new Runnable() { @Override public void run() { if (delegate.isCanceled()) { // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation. callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled")); } else { callback.onResponse(ExecutorCallbackCall.this, response); }}}); } @Override public void onFailure(Call<T> call, final Throwable t) { callbackExecutor.execute(new Runnable() { @Override public void run() { callback.onFailure(ExecutorCallbackCall.this, t); }}); }}); } @Override public Response<T> execute() throws IOException { return delegate.execute(); }... }Copy the code

Let’s finally take a look at the generic types in the CallAdapter, looking directly at the code comments

Public interface CallAdapter<R, T> {/** * responseType(); /** * responseType(); /** * HttpServiceMethod invoke a method that is triggered every time you pass Retrofit, */ T adapt(Call<R> Call); abstract class Factory { /** * Returns a call adapter for interface methods that return {@code returnType}, or null if it * cannot be handled by this factory. */ public abstract @Nullable CallAdapter<? ,? > get(Type returnType, Annotation[] annotations, Retrofit retrofit); /** * index 1 of {@code Map<String,? extends Runnable>} returns {@code Runnable}. */ protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); } /** * A<? > --> A * {@code List<? extends Runnable>} returns {@code List.class}. */ protected static Class<? > getRawType(Type type) { return Utils.getRawType(type); }}}Copy the code

Combining CallAdapter interface look, DefaultCallAdapterFactory why direct new CallAdapter < Object, Call
>(){}, the new adapt method returns Callwith a generic eraser, and returns Call without error.

3, the Convert

HttpServiceMethod holds the responseConvert (responseType) obtained from the CallAdapter

Converter<ResponseBody, ResponseT> responseConverter =  createResponseConverter(retrofit, method, responseType);
Copy the code

createResponseConverter

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); }}Copy the code

BuiltInConverters is added by default when building Retrofit, which inherits from Converter.Factory. Our own custom also inherits from the Converter.Factory class.

public interface Converter<F, T> { @Nullable T convert(F value) throws IOException; Abstract class Factory {// when we get responseConvert, This is then passed to OkHttpCall public @Nullable Converter<ResponseBody,? > responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } parseParameterAnnotation public @Nullable Converter<?} parseParameterAnnotation public @Nullable Converter<? , RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; } // When processing annotations such as FieldMap, see parseParameterAnnotation method public @Nullable Converter<? , String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; }}Copy the code

The common GsonConvert is as follows, Retrofit mainly uses Convert to wrap the request parameters and Convert the returned data into the format we define

Public final class GsonConverterFactory extends Converter.Factory {public final class GsonConverterFactory extends Converter. Public static GsonConverterFactory create() {return create(new Gson()); } public static GsonConverterFactory create(Gson Gson) {// Use the factory method to pass in the Gson object and configure the Gson object. return new GsonConverterFactory(gson); // -> implements Converter<T, RequestBody> } private final Gson gson; private GsonConverterFactory(Gson gson) { this.gson = gson; } @Override public Converter<ResponseBody, ? > responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); } @Override public Converter<? , RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); // -> implements Converter<ResponseBody, T> } }Copy the code

4. Send the request

From the end of the first point, you can see that the request is initiated by the CallAdapter adapt method, which by default returns the ExecutorCallbackCall and triggers its enQueue method, The real internal call is OkHttpCall’s enQueue method.

@Override public void enqueue(final Callback<T> callback) { checkNotNull(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 { call = rawCall = createRawCall(); // Comment 1..... Call.queue (new okhttp3.Callback() {@override public void onResponse(okhttp3. call call, okhttp3.Response rawResponse) { Response<T> response; try { 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); }... }Copy the code

We focus on comment 1 and 2, comment 1

private okhttp3.Call createRawCall() throws IOException { okhttp3.Call call = callFactory.newCall(requestFactory.create(args)); // Here is the parameter passed to invoke, If (call == null) {throw new NullPointerException(" call.factory returned null."); } return call; }Copy the code

Requestfactory. crate

 for (int p = 0; p < argumentCount; p++) {
      argumentList.add(args[p]);
      handlers[p].apply(requestBuilder, args[p]);
    }
Copy the code

The apply method is called, and internally the Apply method matches the parameter values to the parameter names. Remember that Retrofit passed requestConvert of the annotation values and wrapped parameter values to the RequestFactory? Here you pass the parameter values and then build the request Url or assemble the parameters, such as the parmaeterHandler.path class

    @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
      if (value == null) {
        throw new IllegalArgumentException(
            "Path parameter \"" + name + "\" value must not be null.");
      }
      builder.addPathParam(name, valueConverter.convert(value), encoded);
    }
Copy the code

The parseResponse method at note 2 is the responseConvert passed to OKHttpCall that calls its convert method to parse the data into the desired type and then returns.

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); . try { 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