Rxjava + retrofit source code analysis

Time:2022-5-25

Rxjava + retrofit how to request the network, the specific usage is not discussed here, this article only talks about some key source code.

The version is as follows:

okhttp                         : "com.squareup.okhttp3:okhttp:3.10.0",
okhttp3_integration            : "com.github.bumptech.glide:okhttp3-integration:[email protected]",
retrofit                       : "com.squareup.retrofit2:retrofit:2.4.0",
converter_gson                 : "com.squareup.retrofit2:converter-gson:2.3.0",
converter_scalars              : "com.squareup.retrofit2:converter-scalars:2.3.0",
converter_protobuf             : "com.squareup.retrofit2:converter-protobuf:2.3.0",
adapter_rxjava2                : "com.squareup.retrofit2:adapter-rxjava2:2.2.0",
logging_interceptor            : "com.squareup.okhttp3:logging-interceptor:3.10.0",
rxjava                         : "io.reactivex.rxjava2:rxjava:2.1.12",
rxandroid                      : "io.reactivex.rxjava2:rxandroid:2.0.2",

1、 First, about the initialization of retrofit:

private void initRetrofit() {
    ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
    retrofit = new Retrofit.Builder()
            . baseurl (baseurl) // set the address
            . client (client. Build()) // set the customized okhttpclient
            .addConverterFactory(ProtoConverterFactory.createWithRegistry(extensionRegistry))
            .addConverterFactory(StringConverterFactory.create())
            .addConverterFactory(GsonConverterFactory.create(buildGson()))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build();
    service = retrofit.create(ApiService.class);
}
.addConverterFactory(ProtoConverterFactory.createWithRegistry(extensionRegistry)).addConverterFactory(StringConverterFactory.create()).addConverterFactory(GsonConverterFactory.create(buildGson())).addCallAdapterFactory(RxJava2CallAdapterFactory.create())

Added data converter and request adapter.

The initialization of retrofit adopts the builder mode.

Retrofit. In this step of builder (), we have obtained a platform, which must be Android (), which will be used later.

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 {
      Class.forName("java.util.Optional");
      return new Java8();
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform();
  }
}

Looking at the last build (); method:

public Retrofit build() {
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }
  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient();
  }
  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor();
  }
  // Make a defensive copy of the adapters and add the default Call adapter.
  List callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
  callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
  // Make a defensive copy of the converters.
  List converterFactories =
      new ArrayList<>(1 + this.converterFactories.size());
  // Add the built-in converter factory first. This prevents overriding its behavior but also
  // ensures correct behavior when using converters that consume all types.
  converterFactories.add(new BuiltInConverters());
  converterFactories.addAll(this.converterFactories);
  return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
      unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}

1. If our customized okhttpclient is not passed in, the default will be used.

2. If no custom callback executor is set, the default platform will be used defaultCallbackExecutor(); Click enter to find that the callback is in the main thread by default:

static class Android extends Platform {
  @Override public Executor defaultCallbackExecutor() {
    return new MainThreadExecutor();
  }
  @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
    if (callbackExecutor == null) throw new AssertionError();
    return new ExecutorCallAdapterFactory(callbackExecutor);
  }
  static class MainThreadExecutor implements Executor {
    private final Handler handler = new Handler(Looper.getMainLooper());
    @Override public void execute(Runnable r) {
      handler.post(r);
    }
  }
}

3. Add the request adapter we set into, and then add a default request adapter.

4. Add to a default data converter, and then add to it by the data converter we set up.

 

2、 After initializing retrofit, look at this sentence again:

service = retrofit.create(ApiService.class);

Apiservice is an interface with the following methods:

@GET
Observable doGet(@Url String url, @HeaderMap Map headers, @QueryMap Map map);

This create method can be said to be the core. It uses dynamic agents.

@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public  T create(final Class service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(service);
  }
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service },
      new InvocationHandler() {
        private final Platform platform = Platform.get();
        @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);
          }
          ServiceMethod serviceMethod =
              (ServiceMethod) loadServiceMethod(method);
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
          return serviceMethod.adapt(okHttpCall);
        }
      });
}

1. First, check whether this is an interface. Only the interface can dynamically proxy it.

2. Whether to initialize and preload the methods in the interface is required. If yes, it will be done. This is a little repetitive with the following. Let’s talk about the following directly.

3. The statement after return is the place of dynamic proxy. It will proxy all methods of the interface, that is, when we call the apiservice method, it will be intercepted, and then go to inoke method to do our own operation.

Dynamic agents will be discussed separately later.

4. Next, look at the invoke method:

(1) First, judge whether the method is a method of object class. If so, do not intercept it and let it go to the original method.

(2) , platform is Android, platform Isdefaultmethod (method) returns false. Ignore it.

(3)、ServiceMethod serviceMethod =(ServiceMethod) loadServiceMethod(method); Get the methods of the interface, analyze the methods of the interface, such as obtaining annotations and parameters, and construct your own servicemethod

(4) . initialize okhttpcall

(5) , call servicemethod Apply (okhttpcall) (because rxjava is adopted, the request will not be made immediately here. It will only be made when it is subscribed. I’ll talk about it later)

 

3、 Loadservicemethod (method):

The builder mode is also used to construct its own servicemethod.

After entering this method, the key sentence is:

result = new ServiceMethod.Builder<>(this, method).build();

First look:

Builder(Retrofit retrofit, Method method) {
  this.retrofit = retrofit;
  this.method = method;
  this.methodAnnotations = method.getAnnotations();
  this.parameterTypes = method.getGenericParameterTypes();
  this.parameterAnnotationsArray = method.getParameterAnnotations();
}

Note: we will explain it in the way defined above:

@GET
Observable doGet(@Url String url, @HeaderMap Map headers, @QueryMap Map map);

1. Hold retrofit with the original method object.

2. Get the annotation on the method. The obtained are:

 

3. Get the parameter type. The obtained parameters are:

 

 

4. Get the annotation above the parameter. The obtained are:

 

 

Look at the build () method again:

public ServiceMethod build() {
  callAdapter = createCallAdapter();
  responseType = callAdapter.responseType();
  if (responseType == Response.class || responseType == okhttp3.Response.class) {
    throw methodError("'"
        + Utils.getRawType(responseType).getName()
        + "' is not a valid response body type. Did you mean ResponseBody?");
  }
  responseConverter = createResponseConverter();
  for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation);
  }
  if (httpMethod == null) {
    throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
  }
  if (!hasBody) {
    if (isMultipart) {
      throw methodError(
          "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
    }
    if (isFormEncoded) {
      throw methodError("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++) {
    Type parameterType = parameterTypes[p];
    if (Utils.hasUnresolvableType(parameterType)) {
      throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
          parameterType);
    }
    Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
    if (parameterAnnotations == null) {
      throw parameterError(p, "No Retrofit annotation found.");
    }
    parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
  }
  if (relativeUrl == null && !gotUrl) {
    throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
  }
  if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
    throw methodError("Non-body HTTP method cannot contain @Body.");
  }
  if (isFormEncoded && !gotField) {
    throw methodError("Form-encoded method must contain at least one @Field.");
  }
  if (isMultipart && !gotPart) {
    throw methodError("Multipart method must contain at least one @Part.");
  }
  return new ServiceMethod<>(this);
}

1. First get the request adapter.

2. Create a converter for the result of the request.

3. Parse the annotation on the method.

4. Construct a parameterhandler array.

5. Judgment of some anomalies.

 

4、 Let’s explain each step next.

1. Get the request adapter first:

private CallAdapter createCallAdapter() {
  Type returnType = method.getGenericReturnType();
  if (Utils.hasUnresolvableType(returnType)) {
    throw methodError(
        "Method return type must not include a type variable or wildcard: %s", returnType);
  }
  if (returnType == void.class) {
    throw methodError("Service methods cannot return void.");
  }
  Annotation[] annotations = method.getAnnotations();
  try {
    //noinspection unchecked
    return (CallAdapter) retrofit.callAdapter(returnType, annotations);
  } catch (RuntimeException e) { // Wide exception range because factories are user code.
    throw methodError(e, "Unable to create call adapter for %s", returnType);
  }
}

(1)、Gets the return type of the method. The return type cannot be void

(2) . obtain the annotation on the method.

(3) , call retrofit The calladapter (ReturnType, annotations) method gets the requested adapter. (all the request adapters we set up before are in the retrofit object)

The key steps are:

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;
  }
}

Skippast is null, so start is 0;
Traverse the request adapter we set to it before, find it according to the comments on the return type and method, and return when it is found. (the calladapter we obtained here isRxJava2CallAdapter

 

2. Create converter for request results:

responseConverter = createResponseConverter()

This is similar to the process of obtaining the requested adapter, so it is omitted here.

 

3. Annotation on parsing method: parsemethodannotation (annotation). We use get, so the following will be called:

parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);

Our value here is empty, so it only goes and returns after the following.

if (this.httpMethod != null) {
        throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
            this.httpMethod, httpMethod);
      }
      this.httpMethod = httpMethod;
      this.hasBody = hasBody;

      if (value.isEmpty()) {
        return;
      }

 

4. Construct parameterhandler array

int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
  Type parameterType = parameterTypes[p];
  if (Utils.hasUnresolvableType(parameterType)) {
    throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
        parameterType);
  }
  Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
  if (parameterAnnotations == null) {
    throw parameterError(p, "No Retrofit annotation found.");
  }
  parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}

 

This is the main method:

parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);

P is the sequence number, parametertype is the parameter type of the method, and parameterannotations is the annotation of the parameter.

I won’t talk about it in detail. What I finally get here is:

 

 I won’t say much about the judgment of some exceptions, such as:

Cannot have more than one parameter annotated with @ URL.

@ path and @ URL annotations cannot be used at the same time.

The parameter type marked by @ querymap must be map

@The key of the parameter of querymap annotation must be string

So far, our servicemethod has been constructed.

 

 

5、 When we go back to the method of agency, we still have two sentences to resolve:

OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);return serviceMethod.adapt(okHttpCall);

Mainly focus on servicemethod adapt(okHttpCall)

T adapt(Call call) {
  return callAdapter.adapt(call);
}

The calladapter here is rxjava2calladapter.

So we come to its adapter method:

@Override public Object adapt(Call call) {
  Observable> responseObservable = isAsync
      ? new CallEnqueueObservable<>(call)
      : new CallExecuteObservable<>(call);
  Observable observable;
  if (isResult) {
    observable = new ResultObservable<>(responseObservable);
  } else if (isBody) {
    observable = new BodyObservable<>(responseObservable);
  } else {
    observable = responseObservable;
  }
  if (scheduler != null) {
    observable = observable.subscribeOn(scheduler);
  }
  if (isFlowable) {
    return observable.toFlowable(BackpressureStrategy.LATEST);
  }
  if (isSingle) {
    return observable.singleOrError();
  }
  if (isMaybe) {
    return observable.singleElement();
  }
  if (isCompletable) {
    return observable.ignoreElements();
  }
  return observable;
}

First, let’s look at isasync. It’s false here. Why? When we created the adapter, it was like this:

RxJava2CallAdapterFactory.create()

public static RxJava2CallAdapterFactory create() {
  return new RxJava2CallAdapterFactory(null, false);
}

The second parameter is isasync

1. Therefore, the responseobservable we created is callexecuteobservable < > (call), (class of synchronous execution)

2. The observable that we just created here is an observable

Pass it in.

3. Finally, the observable is sent out.

service = retrofit.create(ApiService.class);
public interface ApiService {
    @GET
    Observable doGet(@Url String url, @HeaderMap Map headers, @QueryMap Map map);
}
service.doGet(url, header, params?.params)

That is, when we call service When doget, it will go to the invoke method of the agent and return an observable

And thatObservable can only be executed when it is subscribed, and we use synchronization, so we need to switch to the sub thread outside.

When being subscribed, the bodyobservable will call subscribeactual:

 

BodyObservable(Observable> upstream) {
  this.upstream = upstream;
}
@Override protected void subscribeActual(Observer super T> observer) {
  upstream.subscribe(new BodyObserver(observer));
}

 

The upstream is the responseobservable just passed in. Call the subscribe method and eventually execute the subscribeactual method of responseobservable.

@Override protected void subscribeActual(Observer super Response> observer) {
  // Since Call is a one-shot type, clone it for each new observer.
  Call call = originalCall.clone();
  observer.onSubscribe(new CallDisposable(call));
  boolean terminated = false;
  try {
    Response response = call.execute();
    if (!call.isCanceled()) {
      observer.onNext(response);
    }
    if (!call.isCanceled()) {
      terminated = true;
      observer.onComplete();
    }
  } catch (Throwable t) {
    Exceptions.throwIfFatal(t);
    if (terminated) {
      RxJavaPlugins.onError(t);
    } else if (!call.isCanceled()) {
      try {
        observer.onError(t);
      } catch (Throwable inner) {
        Exceptions.throwIfFatal(inner);
        RxJavaPlugins.onError(new CompositeException(t, inner));
      }
    }
  }
}

We mainly look at responseresponse = call. execute(); Call is the custom okhttpcall we sent in

In call Inside execute():

.
.
.
call = rawCall;
if (call == null) {
  try {
    call = rawCall = createRawCall();
  } catch (IOException | RuntimeException | Error e) {
    throwIfFatal(e); //  Do not assign a fatal error to creationFailure.
    creationFailure = e;
    throw e;
  }
}
.
.
.
return parseResponse(call.execute());

Createrawcall() gets okhttp3 Call,call. Execute () is the network request of okhttp.

We mainly look at how to get okhttp3 Call and parseresponse method for parsing the request result.

private okhttp3.Call createRawCall() throws IOException {
  okhttp3.Call call = serviceMethod.toCall(args);
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

In servicemethod:

/** Builds an HTTP request from method arguments. */
okhttp3.Call toCall(@Nullable Object... args) throws IOException {
  RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
      contentType, hasBody, isFormEncoded, isMultipart);
  @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
  ParameterHandler[] handlers = (ParameterHandler[]) parameterHandlers;
  int argumentCount = args != null ? args.length : 0;
  if (argumentCount != handlers.length) {
    throw new IllegalArgumentException("Argument count (" + argumentCount
        + ") doesn't match expected count (" + handlers.length + ")");
  }
  for (int p = 0; p < argumentCount; p++) {
    handlers[p].apply(requestBuilder, args[p]);
  }
  return callFactory.newCall(requestBuilder.build());
}

The main method is to construct the request and then use okhttp3 Call. Factory create okhttp3 Call, and we built the parameterhandler of servicemethod before[] handlers participated in the construction of the request, mainly adding the previously resolved parameters, such as path and header information, to the request.

 

Take another look at the parseresponse method of the request result:

Key statements:

T body = serviceMethod.toResponse(catchingBody);

Look at the toresponse method in servicemethod:

/** Builds a method return value from an HTTP response body. */
R toResponse(ResponseBody body) throws IOException {
  return responseConverter.convert(body);
}

Here we use the data converter we set up before to convert the results.

 

The above is the general process.

 

Please indicate:https://www.cnblogs.com/tangZH/p/13723480.html

 

Recommended Today

JS generate guid method

JS generate guid method https://blog.csdn.net/Alive_tree/article/details/87942348 Globally unique identification(GUID) is an algorithm generatedBinaryCount Reg128 bitsNumber ofidentifier , GUID is mainly used in networks or systems with multiple nodes and computers. Ideally, any computational geometry computer cluster will not generate two identical guids, and the total number of guids is2^128In theory, it is difficult to make two […]