A, simple use
Details of the process and method are as follows:Using the tutorial
Simple code example:
// Step 1: Create an interface class
public interface WanAndroidService {
@GET("article/list/{index}/json")
Call<JsonObject> getArticles(@Path("index") int index);
}
// Step 2: Build a Retrofit instance
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://wanandroid.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// Step 3: Create a proxy object for the interface class using the retrofit.create method
WanAndroidService wanAndroidService = retrofit.create(WanAndroidService.class);
// Step 4: Call the interface proxy object method to return an instance of the Call class or another custom request class
Call<JsonObject> articles = wanAndroidService.getArticles(0);
// Step 5: Call the synchronous or asynchronous request method of the Call class instance to initiate the network request (.body() method is the entity data object returned after the successful request).
JsonObject body = articles.execute().body();
Copy the code
Two, principle analysis
2.1. Create interface objects
2.1.1, the source code
Steps 1 and 2 are simple: create an interface class and then build an instance of the Retrofit class based on the actual situation
Source code analysis from Step 3Retrofit.create
Method start:
public <T> T create(final Class<T> service) {
// Check whether the interface has type variables. If yes, an exception is thrown
// If Retrofit instances have immediate validation, annotations are parsed and the results are cached for later use.
validateServiceInterface(service);
return (T)
// Use the dynamic proxy method to create proxy objects
Proxy.newProxyInstance(
service.getClassLoader(),
newClass<? >[] {service},new InvocationHandler() {
// Platform information class object
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method comes from the Object class, the default implementation of the Object class method is called without any processing.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args); } args = args ! =null ? args : emptyArgs;
//isDefaultMethod(method) to check if java8 is the default implementation method
// If so, the method implementation is called without doing anything
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
// Request the actual invocation logic of the method: loadServiceMethod(method).invoke(args); }}); }Copy the code
2.1.2, summary
-
Use JDK dynamic proxy technology to generate proxy objects where we define the request interface.
-
The invocation of an interface method ends up calling the Invoke method of InvocationHandler.
-
Invoke (method).invoke(args) if the invoke method internal interface is valid (non-generic interface), is not an Object method, and the interface method does not have a default implementation (JAVA8 starts to support it), loadServiceMethod(method).invoke(args) is finally called; Method begins parsing the encapsulated network request.
2.2. Interface method invocation
Invoke loadServiceMethod(method).invoke(args); Method, the specific source code analysis is as follows:
2.2.1, the source code
loadServiceMethod(method)
A. Parse annotations to generate ServiceMethod objects (loadServiceMethod(method)
)
ServiceMethod<? > loadServiceMethod(Method method) {// Fetch from cache and return from cacheServiceMethod<? > result = serviceMethodCache.get(method);if(result ! =null) return result;
synchronized (serviceMethodCache) {
//double check checks the cache again
result = serviceMethodCache.get(method);
if (result == null) {
// Start parsing method annotations to generate ServiceMethod objects
result = ServiceMethod.parseAnnotations(this, method);
// Cache the result of this method's ServiceMethod parsing. The next request can be obtained directly from the cacheserviceMethodCache.put(method, result); }}return result;
}
Copy the code
B. Parse the annotations and return the ServiceMethod object (ServiceMethod.parseAnnotations(this, method)
)
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
// Create a RequestFactory object that will parse the annotations to GET the network request data and verify the validity of each annotation (e.g., request method annotations must be default GET/POST/... Etc check)
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
// Get the return parameter type
Type returnType = method.getGenericReturnType();
// Verify the return parameter type. The type cannot be the following:
//1, type variable type (T,E, etc.), wildcard expression,
//2, the parameter type of the parameter type <> cannot have type variables or wildcards. >,
// Element type cannot have type variable or wildcard type.
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(
method,
"Method return type must not include a type variable or wildcard: %s",
returnType);
}
// The return type cannot be void. Class
if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}
// Verify annotations are resolved by calling the parseAnnotations method of HttpServiceMethod
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
Copy the code
C. Continue parsing the annotations and return the HttpServiceMethod object (HttpServiceMethod.parseAnnotations(this, method, requestFactory)
)
// Key code
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) {...boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false;
Annotation[] annotations = method.getAnnotations();
Type adapterType;
if (isKotlinSuspendFunction) {
......
} else {
// Gets the return parameter type of the method
adapterType = method.getGenericReturnType();
}
// Create the request adapter (internally via calladater.Factory, if not custom, and the method return value is Call; Using DefaultCallAdapterFactory CallAdapter creation; You can change the default behavior by adding a Factory via Retrofit configuration)CallAdapter<ResponseT, ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); Type responseType = callAdapter.responseType(); .// Create a response data converter (process similar to request adapter creation)
Converter<ResponseBody, ResponseT> responseConverter =
createResponseConverter(retrofit, method, responseType);
okhttp3.Call.Factory callFactory = retrofit.callFactory;
if(! isKotlinSuspendFunction) {// Return callStuck (); // Return callStuck ().
//1, requestFactory: internal store annotation parsed request method, request parameters, URL and so on data;
// callFactory: OkHttpClient;
// select * from responseConverter;
//4, callAdapter: request adapter, you can customize the implementation of thread switching logic, Android default adapter function is asynchronous request response callback switch to the main thread)
return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
......
} else{... }}Copy the code
The defaultDefaultCallAdapterFactory
Request adapter create factory source code as follows:
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
private final @Nullable Executor callbackExecutor;
/ / if the Android default incoming is Platform MainThreadExecutor instance (can be modified through the Retrofit configuration)
DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
this.callbackExecutor = callbackExecutor;
}
@Override
public @NullableCallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit) {// If the return type is not call. class, null is returned
if(getRawType(returnType) ! = Call.class) {return null;
}
// Check the return parameter type
if(! (returnTypeinstanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
}
// Get the type of the Call< XXX > parameter
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
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) {
ExecutorCallbackCall: ExecutorCallbackCall: ExecutorCallbackCall: ExecutorCallbackCall: ExecutorCallbackCall: ExecutorCallbackCall: ExecutorCallbackCall
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) {
Objects.requireNonNull(callback, "callback == null");
delegate.enqueue(
new Callback<T>() {
@Override
public void onResponse(Call<T> call, final Response<T> response) { // Switch threads (default is to switch to the main thread)
callbackExecutor.execute(
() -> {
if (delegate.isCanceled()) {
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(() -> callback.onFailure(ExecutorCallbackCall.this, t)); }}); }@Override
public boolean isExecuted(a) {
return delegate.isExecuted();
}
@Override
public Response<T> execute(a) throws IOException {
returndelegate.execute(); }... }}Copy the code
ServiceMehod.invoke(Object args)
LoadServiceMethod (Method) returns an instance of an HttpServerMethod subclass. Invoke is implemented in HttpServerMethod.
@Override
final @Nullable ReturnT invoke(Object[] args) {
// Create an OkHttpCall instance
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
// When you call the adapt method, Java will eventually call the Adapt method of the callAdapter (Kotlin is similar and slightly different), which will eventually call the callAdapter adapt method
return adapt(call, args);
}
protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);
static final class CallAdapted<ResponseT.ReturnT> extends HttpServiceMethod<ResponseT.ReturnT> {
private final CallAdapter<ResponseT, ReturnT> callAdapter;
CallAdapted(
RequestFactory requestFactory,
okhttp3.Call.Factory callFactory,
Converter<ResponseBody, ResponseT> responseConverter,
CallAdapter<ResponseT, ReturnT> callAdapter) {
super(requestFactory, callFactory, responseConverter);
this.callAdapter = callAdapter;
}
@Override
protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
As described in the previous step, the Android platform returns an ExecutorCalbackCall by default (the internal default implementation simply cuts asynchronous requests back to the main thread), Of course, we can change the return value or the default behavior by configuring the callbackExecutor and adding a custom callAdapter
returncallAdapter.adapt(call); }}Copy the code
2.2.2,
1, loadServiceMethod (method). The invoke (args); When the call ends, an instance of OKHttpCall is returned by default. (The Android platform returns ExecutorCalbackCall, which decorates the OKHttpCall instance and extends the OKHttpCall asynchronous request callback processing logic.)
You can change the default behavior of switching asynchronous request callbacks to the main thread by setting up the asynchronous callback processor with retrofit. Builder’s callbackExecutor method.
Retrofit.Builder addCallAdapterFactory can be used to add adapter factories, change and extend method return values and method execution, asynchronous request callback thread switching, etc.
Retrofit+RxJava is a new adaptor factory that extends functionality by changing method return values.
2.3. Initiate network requests
OkHttpCall(the default ExecutorCalbackCall is only a decorator, and eventually method calls will be called into OkHttpCall)
(If a new CallAdapter is added and the return value is changed, the OkHttpCall method is still called to implement the network request. The process is similar to the default behavior.)
2.3.1, source
A synchronous request
Call. The execute () method
@Override
public Response<T> execute(a) throws IOException {
okhttp3.Call call;
synchronized (this) {
// A Call can only be executed once, otherwise it can be thrown once
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
// Get the okHttp3. Call instance, created by callFactory and requestFactory
// The okHttpClient. newCall(Request Request) method is eventually called to create an okHttp3. Call instance
call = getRawCall();
}
if (canceled) {
call.cancel();
}
// Finally Call the execute method of okHttp3. Call to initiate a synchronization request
ParseResponse parses the response data
return parseResponse(call.execute());
}
Copy the code
2.3.2 Asynchronous request
Call.enqueue(final Callback\
Callback) method
@Override
public void enqueue(final Callback<T> callback) {
Objects.requireNonNull(callback, "callback == null");
okhttp3.Call call;
Throwable failure;
synchronized (this) {
// A Call can execute only one request
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
call = rawCall;
failure = creationFailure;
if (call == null && failure == null) {
try {
// Get the okHttp3. Call instance, created by callFactory and requestFactory
// The okHttpClient. newCall(Request Request) method is eventually called to create an okHttp3. Call instance
call = rawCall = createRawCall();
} catch(Throwable t) { throwIfFatal(t); failure = creationFailure = t; }}}if(failure ! =null) {
callback.onFailure(this, failure);
return;
}
if (canceled) {
call.cancel();
}
// Invoke the enqueue method of okhttp3.Call to initiate an asynchronous request
call.enqueue(
new okhttp3.Callback() {
@Override
public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
Response<T> response;
try {
Parse the response data
response = parseResponse(rawResponse);
} catch (Throwable e) {
throwIfFatal(e);
callFailure(e);
return;
}
try {
// Call back the result of parsing the response data
callback.onResponse(OkHttpCall.this, response);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great}}@Override
public void onFailure(okhttp3.Call call, IOException e) {
// The callback failed
callFailure(e);
}
private void callFailure(Throwable e) {
try {
callback.onFailure(OkHttpCall.this, e);
} catch (Throwable t) {
throwIfFatal(t);
t.printStackTrace(); // TODO this is not great}}}); }Copy the code
Analytical data
OkHttpCall. ParseResponse (okhttp3. Response rawResponse) method
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Delete the request body body data so that the Response result can be passed later with a Response without the body data
rawResponse =
rawResponse
.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
// Determine the request status code
if (code < 200 || code >= 300) {
// Not 200, request failed
try {
// Cache error response body data
ResponseBody bufferedBody = Utils.buffer(rawBody);
// Generate an instance of Retrofit's Response and return it
return Response.error(bufferedBody, rawResponse);
} finally{ rawBody.close(); }}if (code == 204 || code == 205) {
rawBody.close();
Return retrofit.response with no data
return Response.success(null, rawResponse);
}
/ / using Okio buffer read data to the response body subject ExceptionCatchingResponseBody (ResponseBody subclass) instances
ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody);
try {
// Parse the body data and return the data entity object we need
T body = responseConverter.convert(catchingBody);
// Return a successful retrofit.Response instance with data
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
catchingBody.throwIfCaught();
throwe; }}Copy the code
2.3.2,
-
The AD request ends up calling either the synchronous or asynchronous methods of okHttp3. Call.
-
The okHttp3. Response Response data returned by the okHttp3. Call request is parsed using the okHttpCall. parseResponse method. Finally invokes responseConverter. Convert (converter conversion method) analytic transformation of different data structure.
-
Data converters can be added through retrofit.Builder’s addConverterFactory method to support parsing different data structures.
-
After the data is parsed, the Response result, success data entity, or failure data is returned wrapped as an instance of Retrofit.Response. Success can be determined by code() of the Retrofit.Response class or isSuccessful() method. The body() method gets the (possibly empty) data entity returned in the successful status of the request.
-
If ConverterFactory is not added, only an object instance of the okHttp3.responseBody class is returned by default. That is, retrofit.response.body () can only be an object of class okHttp3.responseBody (java8+ can also be Optional), At the same time the return value of the actual parameter types defined interface method can only be okhttp3. ResponseBody. Class.
Third, the final conclusion
-
The Retrofit framework allows developers to describe network requests as interface annotations, and then actually implement interface method invocation logic as JDK dynamic proxies. The call to the method parses the interface annotations and finally encapsulates the network request resolution results and associated configuration into an OkHttpCall.
-
The OkHttpCall call method initiates the network request and eventually invokes either the synchronous or asynchronous request methods in the OKHttp3. call class to implement the actual network request (that is, the network request is implemented through the OkHttp framework).
-
The CallAdapter data request adapter is designed to extend the capabilities of OkHttpCall and will eventually organize network requests through OkHttpCall.
-
The purpose of the ConverAdapter data converter is to convert the data structure into what we want to be an entity object after the request is successful.
Relevance knowledge
Java dynamic proxy knowledge
Retrofit framework is used
Java Type knowledge description
5. Refer to the article
How does Android Retrofit work
Retrofit process analysis