How to implement interface calls within Retrofit
We all know Retrofit is by defining an interface, taking an instance of the interface, and calling the corresponding method. We start with the process to see how the source code works.
1. First get a customized service instance by retrofit.create method, source code above:
Here you can see that the object of the custom service is generated by dynamic proxy. And when calling the custom interface method, there is one method executed. So take a look at the loadServiceMethod method:
You can see that there was a ServiceMethod object soldier returning at 1, then look in step by step:
HttpServiceMethod.parseAnnotations:
An HttpServiceMethod object is returned here, so what is called in the dynamic proxy is the HttpServiceMethod#invoke method with the following source code:
OkHttpCall is an encapsulation of OkHttp. The details of the calling interface are all implemented in it. You can see the enqueue method inside:
@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(); } catch (Throwable t) { throwIfFatal(t); failure = creationFailure = t; } } } if (failure != null) { callback.onFailure(this, failure); return; } if (canceled) { call.cancel(); } call.enqueue(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); } private void callFailure(Throwable e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { t.printStackTrace(); } } }); }
Of course, we can see from the above that the HttpServiceMethod#invoke method only passes OkHttpCall as a parameter to the callAdapter.adapt method, so the real time to call the interface is in this.
Summary: When an interface method inside a service calls the HttpServiceMethod.invoke method.
Judgment of the type of request
So we have a clear idea of the process that was invoked, so when did the type of request be determined? It was actually determined when the RequestFactory was created.
In the ServiceMethod.parseAnnotations method:
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
Step in and call the RequestFactory.Builder.parseMethodAnnotation method:
You can see here that the type of request is already based on the annotation of the interface method.
The role of callAdapter and responseConverter
In fact, many people may not understand what these two really do. We have the following code to initialize retrofit:
In the diagram, callAdapter and responseConverter are added respectively.
Let me just tell you a little about their usefulness. When we define an interface, there may be code like this:
The return value here is Observable < TestStringResponse >, where Observable is parsed and returned by the callAdapter because the default return value for retrofit is Call. The type inside the angle brackets is parsed and returned by the responseConverter according to the responseBody.
callAdapter can be understood as an adapter that returns a value.
responseConverter can be interpreted as a converter requesting data back
1,callAdapter
So let's see how he says it works. Back to the HttpServiceMethod.parseAnnotations method above:
In this method, callAdapter and responseConverter are obtained from the calling interface at position 1 and 2 respectively.
1 The code is as follows:
Return Type is the return type of the interface defined, such as Observable < TestStringResponse > as we exemplified above.
Annotations are annotations for this interface.
Continue to look at retrofit.callAdapter:
Continue looking down at the nextResponseBodyConverter:
You can see that the converterFactories collection is traversed and the get method in the collection element is called to get an instance of the Converter interface.
ConverFactories is a collection of adapter factories where get methods are used to get specific adapters.
So what about this converterFactories? As you can see from the code, it is assigned in the constructor, which is called in Builder.build, as follows:
The code at 1 creates a new set of CallAdapter.Factory whose parameter this.callAdapterFactories is the adapter factory that addCallAdapterFactory was added to when we created the Retrofit instance.
You can see that an adapter factory is joined by default by platform.defaultCallAdapterFactories(callbackExecutor). This is actually an instance of DefaultCallAdapterFactory. Let's look at its source code:
The responseType method will be used later. Press the Not Table here first.
You can see that there are restrictions in this get method that return valid adapters only if the return value is of type call. In the adapter method, calls are returned directly.
Let's review where the adapt method was called, calling the HttpServiceMethod.invoke method when we called the interface we defined, where the callAdapter.adapt method was called.
The same is true when we add the callAdapter for rxjava. Let's look at the RxJava2CallAdapterFactory.get method:
@Override public @Nullable CallAdapter<?, ?> get( Type returnType, Annotation[] annotations, Retrofit retrofit) { Class<?> rawType = getRawType(returnType); if (rawType == Completable.class) { // Completable is not parameterized (which is what the rest of this method deals with) so it // can only be created with a single configuration. return new RxJava2CallAdapter(Void.class, scheduler, isAsync, false, true, false, false, false, true); } boolean isFlowable = rawType == Flowable.class; boolean isSingle = rawType == Single.class; boolean isMaybe = rawType == Maybe.class; if (rawType != Observable.class && !isFlowable && !isSingle && !isMaybe) { return null; } boolean isResult = false; boolean isBody = false; Type responseType; if (!(returnType instanceof ParameterizedType)) { String name = isFlowable ? "Flowable" : isSingle ? "Single" : isMaybe ? "Maybe" : "Observable"; throw new IllegalStateException(name + " return type must be parameterized" + " as " + name + "<Foo> or " + name + "<? extends Foo>"); } Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType); Class<?> rawObservableType = getRawType(observableType); if (rawObservableType == Response.class) { if (!(observableType instanceof ParameterizedType)) { throw new IllegalStateException("Response must be parameterized" + " as Response<Foo> or Response<? extends Foo>"); } responseType = getParameterUpperBound(0, (ParameterizedType) observableType); } else if (rawObservableType == Result.class) { if (!(observableType instanceof ParameterizedType)) { throw new IllegalStateException("Result must be parameterized" + " as Result<Foo> or Result<? extends Foo>"); } responseType = getParameterUpperBound(0, (ParameterizedType) observableType); isResult = true; } else { responseType = observableType; isBody = true; } return new RxJava2CallAdapter(responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false); }
You can see here that the return value of the interface method is also determined, and only if the condition is met will the RxJava2CallAdapter be returned.
Continue with the RxJava2CallAdapter.adapt method:
You can see that the CallExecuteObservable object is generated from the call object, which inherits from Observable. And at the end, a RxJavaPlugins.onAssembly(observable) is returned. The return value in this method is Observable, so this implements the conversion from the default call type to the returned Observable type.
Let's continue with the source code for CallExecuteObservable:
final class CallExecuteObservable<T> extends Observable<Response<T>> { private final Call<T> originalCall; CallExecuteObservable(Call<T> originalCall) { this.originalCall = originalCall; } @Override protected void subscribeActual(Observer<? super Response<T>> observer) { // Since Call is a one-shot type, clone it for each new observer. Call<T> call = originalCall.clone(); CallDisposable disposable = new CallDisposable(call); observer.onSubscribe(disposable); if (disposable.isDisposed()) { return; } boolean terminated = false; try { Response<T> response = call.execute(); if (!disposable.isDisposed()) { observer.onNext(response); } if (!disposable.isDisposed()) { terminated = true; observer.onComplete(); } } catch (Throwable t) { Exceptions.throwIfFatal(t); if (terminated) { RxJavaPlugins.onError(t); } else if (!disposable.isDisposed()) { try { observer.onError(t); } catch (Throwable inner) { Exceptions.throwIfFatal(inner); RxJavaPlugins.onError(new CompositeException(t, inner)); } } } } private static final class CallDisposable implements Disposable { private final Call<?> call; private volatile boolean disposed; CallDisposable(Call<?> call) { this.call = call; } @Override public void dispose() { disposed = true; call.cancel(); } @Override public boolean isDisposed() { return disposed; } } }
I don't really understand when the subscribeActual method was called. Look at the translation in the source notes below:
Operator implementations, including source and intermediate, should implement this method to execute the necessary business logic and process the incoming {@link Observer}. No plug-in hooks need to be called on the current {@code Observable} instance or {@code Observer};Before calling this method, {@link #subscribe(Observer)} has applied all hooks and basic protection.
I don't understand, but I think this method should be called when I subscribe (it's tested to be a subscription before it triggers).
call.execute() is called in this method, so we know that the call here is actually an instance of OkHttpCall type, so let's take a look at the source code of execute in it:
@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 { 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(); } return parseResponse(call.execute()); }
This is where the createRawCall method is called to create a real request and call it.
So let's first summarize:
When we call an interface method in a custom service, retrofit automatically looks for the corresponding callAdapter based on the return value defined in the interface method.
Then call the adapt method inside the callAdapter to convert the call type to the type you want.
2,responseConverter
Perhaps a careful student noticed when the callAdapter was mentioned above. In the CallExecuteObservable class, there is a sentence: observer.onNext(response);It's clear here that a response is passed downstream, but when we subscribe to an observer in general, it's a custom type of entity. So let's take a closer look at this.
As we know from the above, we have returned parseResponse(call.execute()) in the OkHttpCall.execute method. In fact, this parseResponse method converts the body we requested to return into an entity we defined. Let's take a look at the source code:
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 { 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; } }
There is one sentence: T body = responseConverter.convert(catchingBody);This is where we turn the returned body into the entity we defined.
ResponsseConverter was created in the HttpServiceMethod.parseAnnotations method. Step by step, it comes in:
Note that there is a call to the callAdapter.responseType method, and I have deliberately labeled a sentence in the previous figure: for example, the returnType is a call, then the returned Bean is here. That's right, what I get here is the type in the angle brackets of the return value of our interface method.
Let's look at the source code for GsonResponseBodyConverter:
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> { private final Gson gson; private final TypeAdapter<T> adapter; GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } @Override public T convert(ResponseBody value) throws IOException { JsonReader jsonReader = gson.newJsonReader(value.charStream()); try { T result = adapter.read(jsonReader); if (jsonReader.peek() != JsonToken.END_DOCUMENT) { throw new JsonIOException("JSON document was not fully consumed."); } return result; } finally { value.close(); } } }
You can see that this is where we use gson to convert ResponseBody to our custom entity.
Let's go back to OkHttpCall. Although the parseResponse method converts the responseBody to a custom entity, the return value is the Response type, but simply sets the Response.body to our entity object. So the return value obtained in the CallExecuteObservable.subscribeActual method is ResponseBut let's go back to the RxJava2CallAdapter.adapt method:
Note the isBody at 1. When isBody is true, the responseObservable is converted to BodyObservable. The isBody variable is passed in the RxJava2CallAdapterFactory.get method:
That is, if we customize whether the type in the angle brackets of interface return values is a Response or a subclass of Result, isBody = true.
Let's go back to BodyObservable and look at the source code:
@Override public void onNext(Response<R> response) { if (response.isSuccessful()) { observer.onNext(response.body()); } else { terminated = true; Throwable t = new HttpException(response); try { observer.onError(t); } catch (Throwable inner) { Exceptions.throwIfFatal(inner); RxJavaPlugins.onError(new CompositeException(t, inner)); } } }
You can see here that response.body() is passed directly down to the downstream. As mentioned above, response.body() is actually a custom entity that we have set up.