Retrofit
Retrofit encapsulates the framework of the web request interface by combining various design patterns, leaving the specific requests to Okhttp.
Simple implementation
The configuration of Retrofit objects requires a series of configurations such as data serialization, thread scheduling, adapters, and so on. The specific configuration is optional. When configuring the data converter, specify the corresponding converter. Otherwise, an error may occur during data serialization.
val client = OkHttpClient.Builder()
.addInterceptor(LoggingInterceptor())
.build()
val retrofit = Retrofit.Builder()
.client(client)
.baseUrl(HttpService.HttpUrl.url)
.addCallAdapterFactory(KotlinCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(SimpleXmlConverterFactory.create())
.addConverterFactory(JaxbConverterFactory.create())
.build()
fun request(a) {
retrofit.create(HttpService::class.java)
.request1()
.enqueue(object : Callback<ResponseData<List<WxArticle>>> {
override fun onResponse(call: Call<ResponseData<List<WxArticle>>>, response: Response<ResponseData<List<WxArticle> > >) {
Log.i("MDY"."onResponse=" + Thread.currentThread().name)
Log.i("MDY"."onResponse: " + response.body().toString())
}
override fun onFailure(call: Call<ResponseData<List<WxArticle>>>, t: Throwable) {
Log.i("MDY"."onFailure: ")}}}Copy the code
When Retrofit was created, the create method was invoked to dynamically generate a proxy class to implement a specific network request.
create
When you create a delegate class Service in Android, the request method must be of the public Abstract type because Service is an interface. Therefore, when the dynamic proxy class created by the Create method in Retrofit calls the corresponding request method in Service, The loadServiceMethod method is executed:
ServiceMethod<? > loadServiceMethod(Method method) { ServiceMethod<? > result = serviceMethodCache.get(method);if(result ! =null) return result;
// Synchronize lock to prevent ServiceMethod from being created more than once
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
/ / analytical Method
result = ServiceMethod.parseAnnotations(this, method); serviceMethodCache.put(method, result); }}return result;
}
Copy the code
Retrofit uses a Map collection to hold the parsed request methods and encapsulate them into the corresponding implementation class ServiceMethod:
private finalMap<Method, ServiceMethod<? >> serviceMethodCache =new ConcurrentHashMap<>()
Copy the code
If the interface is called for the first time, the method is resolved using parseAnnotations of ServiceMethod. The RequestFactory’s parseAnnotations method is called to resolve method annotations, parameter annotations, return types, etc.
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
/ /... Determine whether the return type is valid
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
Copy the code
The HttpServiceMethod method is an abstract class, and the Corresponding CallAdapter, Converter, and callFactory objects are retrieved from the parseAnnotations method.
createCallAdapter
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter( Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {
try {
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); }}Copy the code
Calling Retrofit’s callAdapter method ends up calling back to the CallAdapterFactory we passed in when we created Retrofit and calling the corresponding GET method to get the callAdapter object.
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
Calling Retrofit’s responseBodyConverter method will eventually call back to the Converter.Factory we passed in when we created Retrofit, And call responseBodyConverter to get Converter
object. The created Converter is called to serialize the data after the response body is retrieved in a later OkHttpCall.
callFactory
okhttp3.Call.Factory callFactory = retrofit.callFactory;
Copy the code
The Call.factory is the OkhttpClient object we passed to Retrofit.
CallAdapted
ServiceMethod and HttpServiceMethod are abstract classes, and what we need is a concrete implementation class, so callCaller comes in. Callcaller inherits from HttpServiceMethod and holds the following objects:
- RequestFactory is the data store class that parses the request method.
- CallFactory OkhttpClient object for specific network requests.
- ResponseConverter Data converter used to serialize the response returned by the request.
- The callAdapter calls the adapter, or the object that holds a call, and enables the initiation of requests and the scheduling of threads.
At this point, we go back to the original Create method. When the request method is called through the dynamic proxy, executing to loadServiceMethod will eventually get a CallStuck object. The invoke method is then called, which is not implemented in CallMorals, as specified in HttpServiceMethod:
@Override final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
Copy the code
There is an OkHttpCall class that is used for specific network requests, internally creating the corresponding RealCall object in OKhttp, and adding synchronous and asynchronous methods that are used in the same way as OKhttp. Since this article is to analyze the principle of Retrofit, so no more explanation, interested can go in to see. This is followed by a callback to the Adapt method of CallSeller:
@Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
Copy the code
The CallAdapter here is the one we created in HttpServiceMethod, and if we’re using RxJava, we’re passing the CallAdapter returned inside the corresponding RxJavaCallAdapterFactory object. When the Adapt method is called, pass in the OkHttpCall created in the Invoke method. Since the corresponding Realcall is encapsulated in OkHttpCall, it’s not hard to guess that the specific network request and resolution must be in the corresponding CallAdapter implementation class.
CallAdapter
The CallAdapter is called the CallAdapter, and when creating a Retrofit object, we can call addCallAdapterFactory(KotlinCallAdapterFactory()), passing in a specified call adaptation factory class to create the specific CallAdapter. The concrete Factory class needs to inherit from the Factory class and return a CallAdapter object through the GET method.
public interface CallAdapter<R.T> {
// Request method return type
Type responseType(a);
// Returns a delegate instance of OkHttpCall, or executes the request
T adapt(Call<R> call);
// As an abstract factory, return a call adapter
abstract class Factory {
public abstract @NullableCallAdapter<? ,? > get(Type returnType, Annotation[] annotations, Retrofit retrofit); . }}Copy the code
Retrofit ADAPTS RxJava by providing the RxJavaCallAdapterFactory factory class. If do not installed, provides the default factory class implements DefaultCallAdapterFactory Retrofit. We mentioned earlier that when Retrofit’s dynamic proxy is called to invoke the corresponding network request, it ends up calling back to the callAdapter adapt method and passing an OkHttpCall instance object. Once we get the instance object, we can execute the specific network request. Here we implement a simple call to the adapter factory class and the corresponding adapter:
class KotlinCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
val rawType = getRawType(returnType)
check(rawType !is Call<*>){
error("Return type wrong")}return KotlinCallAdapter(returnType)
}
}
class KotlinCallAdapter(private val returnType: Type) : CallAdapter<Any, Any> {
override fun responseType(a): Type {
return getParameterUpperBound(0, returnType as ParameterizedType)
}
override fun adapt(call: Call<Any>): Any {
return call
}
private fun getParameterUpperBound(index: Int, type: ParameterizedType): Type {
valtypes = type.actualTypeArguments require(! (index <0 || index >= types.size)) { "Index " + index + " not in range [0," + types.size + ") for " + type }
val paramType = types[index]
return if (paramType is WildcardType) {
paramType.upperBounds[0]}else paramType
}
}
Copy the code
After implementing the KotlinCallAdapterFactory object, we can directly use:
fun request(a) {
retrofit.create(HttpService::class.java)
.request1()
.enqueue(object : Callback<ResponseData<List<WxArticle>>> {
override fun onResponse(call: Call<ResponseData<List<WxArticle>>>, response: Response<ResponseData<List<WxArticle> > >) {
Log.i("MDY"."onResponse=" + Thread.currentThread().name)
Log.i("MDY"."onResponse: " + response.body().toString())
}
override fun onFailure(call: Call<ResponseData<List<WxArticle>>>, t: Throwable) {
Log.i("MDY"."onFailure: ")}}}Copy the code
This is just a simple implementation, the details such as thread switching, parameter judgment, data callback and so on can be implemented freely. Through the Retrofit own DefaultCallAdapterFactory and Rxjava RxJavaCallAdapterFactory factory class that can be a specific reference and understand the details of the implementation and thinking.
Through the Handler returns data in the DefaultCallAdapterFactory post to the main thread.
The Retrofit process is pretty much covered here, but I thought I’d add a Converter, which translates REQUESTS and responses related to HTTP interactions into concrete objects.
Converter
Converter can be thought of as a data Converter, and specific data types such as JSON, XML, etc. need corresponding converters to implement. When creating Retrofit objects, we can through addConverterFactory (GsonConverterFactory. The create () method is passed a different data parser, you can pass multiple, internal will parse the data according to the data type. Data converters in Retrofit are implemented using abstract factories. The creation of a concrete Factory class inherits the Factory class and implements the responseBodyConverter and requestBodyConverter methods.
When the request parameters are parsed, the requestBodyConverter method is called to get an instance of the Converter and the convert method is called to convert it into a RequestBody RequestBody. When the response is received in OkHttpCall, the responseBodyConverter method is called to get an instance of the Converter and the convert method is called to convert the ResponseBody to the corresponding return data type.
public interface Converter<F.T> {
@Nullable T convert(F value) throws IOException;
abstract class Factory {
public @NullableConverter<ResponseBody, ? > responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {return null;
}
public @NullableConverter<? , RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {return null;
}
public @NullableConverter<? , String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {return null; }... }}Copy the code
It is true that the Converter and The CallAdapter are both designed in an abstract factory way, which allows us to set up the data Converter and call the adapter freely, and achieves high cohesion and low coupling.
There is basically no need to customize Converter. Retrofit provides a wealth of Converter implementation classes:
- GsonConverterFactory Gson converter for JSON data format.
- SimpleXmlConverterFactory Xml converter, which has been abandoned.
- JaxbConverterFactory new Xml converter.
There are other types that you can look at in the Converter implementation class
Retrofit provides a way to write converters dynamically by declaring the return value type of the request in the interface: JsonAndXmlConverters
conclusion
A complete process in Retrofit consists of the following steps:
- Retrofit can be created to configure OkhttpClient, CallAdapter adapter, Converter, etc.
- Call the create method to dynamically generate a proxy class to initiate a network request that is internally called to
loadServiceMethod
Method to obtain aServiceMethod
Object. - after
ServiceMethod
theparseAnnotations
To parse the request method’s annotations, parameter annotations, return types, and other data passed toHttpServiceMethod
. HttpServiceMethod
Method, return type, annotation, etcCallAdapter
andConveter
Implements the class and returns oneCallAdapted
Object.- The callback
CallAdapted
theinvoke
Method to get the wrapper class for the network requestOkHttpCall
Object. OkHttpCall
Object will be passed toCallAdapter
Object to initiate a network request and inOkHttpCall
In the callConveter
Parse the response.
Retrofit abstracts HTTP requests into Java interfaces through a number of design patterns. Annotations are used to describe and configure network requests. Service interface methods are implemented through dynamic proxies. Through the abstract factory pattern, we can set up the CallAdapter for request initiation, thread scheduling, and so on. It can be said that Retrofit’s code embodies the beauty of design and is very helpful in reading the source code and understanding design patterns. I’ll leave you there, and I’ll learn more about his design patterns with Retrofit.