1. Introduction
What is Retrofit? Retrofit is an encapsulation of a RESTful design framework for HTTP web requests, and is currently the most popular library for web requests on Android. Just as Volley is Google’s official wrapper for HttpURLConnection and HttpClient, or as many developers on Github are publishing wrapper libraries for HttpURLConnection and OkHttp, Retrofit is also published by Square as a wrapper over OkHttp, so the underlying network requests in Retrofit are done by OkHttp.
2. Basic use
The steps to use Retrofit are simple, starting with creating a network request interface description file.
// Create an interface file ApiService. Retrofit abstracts each Http interface request into a method in the interface. public interface ApiService { @GET("api")
Call<WeatherBean> getWeather(@Query("version") String key, @Query("cityid") String city);
}
Copy the code
You can then create a Retrofit object.
Retrofit = new Retrofit.builder () Retrofit = new Retrofit.builder ()"http://www.tianqiapi.com/") / / add the response data converter factory, here're the results into Gson Gson will get the response object. The addConverterFactory (GsonConverterFactory. The create ()) / / add network request adapter factory, Here is the translate the Rxjava RxJava2 will Call observables. AddCallAdapterFactory (RxJava2CallAdapterFactory. Create ()). The build (); ApiService service = retrofit.create(ApiService. Class); // Call<WeatherBean> responseCall = service.getWeather("v1"."101110101"); Responsecall.execute (); Callback<WeatherBean>() {@override public void onResponse(Call<WeatherBean> Call, Response<WeatherBean> Response) {responseStr = response.body().tostring (); Log.e("result", responseStr); } @override public void onFailure(Call<WeatherBean> Call, Throwable t) {"result", t.getMessage()); }});Copy the code
Pay a little attention to these two lines:
/ / add the response data converter factory, here're the results into Gson Gson will get the response object. The addConverterFactory (GsonConverterFactory. The create ()) / / add network request adapter factory, Here is RxJava2 will Call into the observables Rxjava addCallAdapterFactory (RxJava2CallAdapterFactory. The create ())Copy the code
The ConverterFactory here represents a data ConverterFactory that converts the data received as a result of the response to the desired type. For example, Gson is commonly used to parse the data returned by the server, and a GsonConverterFactory has been added here. The CallAdapterFactory represents the factory of the network request adapter, which corresponds to the return type of the interface request method, and converts the default ExecutorCallbackCall to a type needed to adapt to different platforms. For example, if you add an RxJava adapter, the interface could be written like this, returning an Observable from RxJava.
@GET("api")
Observable<WeatherBean> getWeather(@Query("version") String key, @Query("cityid") String city);
Copy the code
3. Source code operation process
The source code in this article is based on Retrofit version 2.6.0, so let’s start by looking at the source code to learn more about Retrofit, again in the order of how to use it.
3.1 Creating Retrofit.Builder.
public Builder() {
this(Platform.get());
}
Builder(Platform platform) {
this.platform = platform;
}
Copy the code
Get a Platform and call the overloaded constructor. Let’s take a look at Platform’s GET method to see what Platform does.
private static final Platform PLATFORM = findPlatform(); // Return static member variables between get methods PLATFORM static PLATFORMget() {
returnPLATFORM; } // Different platforms return different private static platformsfindPlatform() {
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();
}
Copy the code
The static member variable PLATFORM is returned by the findPlatform method. The static member variable PLATFORM is returned by the findPlatform method. The static member variable PLATFORM is returned by the findPlatform method. Return different platforms depending on the Platform, here are Android and Java, and let’s take a closer look at the Returned Android object on the Android Platform.
Static class Android extends Platform {// Check whether the default method is API24false@ignorejrerequirement // Guarded by API check. @Override Boolean isDefaultMethod(Method Method) {if (Build.VERSION.SDK_INT < 24) {
return false;
}
returnmethod.isDefault(); } // Initialize the default main thread Executor @override public ExecutordefaultCallbackExecutor() {
returnnew MainThreadExecutor(); } // Initialize the default network request adapter factory set @override List<? extends CallAdapter.Factory> defaultCallAdapterFactories( @Nullable Executor callbackExecutor) {if (callbackExecutor == null) throw new AssertionError();
DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);
returnBuild.VERSION.SDK_INT >= 24 ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory) : singletonList(executorFactory); } // The default network request adapter factory set size @override intdefaultCallAdapterFactoriesSize() {
returnBuild.VERSION.SDK_INT >= 24 ? 2:1; } // Initialize the default data converter factory set @override List<? extends Converter.Factory>defaultConverterFactories() {
returnBuild.VERSION.SDK_INT >= 24 ? singletonList(OptionalConverterFactory.INSTANCE) : Collections.<Converter.Factory>emptyList(); } // The default data converter factory set size @override intdefaultConverterFactoriesSize() {
returnBuild.VERSION.SDK_INT >= 24 ? 1:0; } // MainThreadExecutor static class MainThreadExecutor implements Executor {private final Handler handler = new Handler(Looper.getMainLooper()); @override public void execute(Runnable r) {// Handle Handler. Post (r); }}}Copy the code
Taking a look at the methods in Android, the main ones are to initialize the functionality required by Retrofit. The defaultCallbackExecutor method initializes the main thread’s executor, which is used by default to return to the main thread after an asynchronous request. DefaultCallAdapterFactories is the default initialization request converter factory set, the default DefaultCallAdapterFactory created a converter factory. DefaultConverterFactories is initialized data converter factory set by default. You can see that Retrofit provides methods here to set up the default data converter factory and network request adapter factory, which are called by default.
3.2 set the baseUrl
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
return baseUrl(HttpUrl.get(baseUrl));
}
public Builder baseUrl(HttpUrl baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
List<String> pathSegments = baseUrl.pathSegments();
if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
}
this.baseUrl = baseUrl;
return this;
}
Copy the code
The baseUrl method is very simple to implement by setting baseUrl to retrofit. Builder. The String method encapsulates the passed String into an HttpUrl object and calls the corresponding overloaded method. It then determines whether the URL ends with a slash or throws an exception if it does not.
3.3 Adding a data converter factory
private final List<Converter.Factory> converterFactories = new ArrayList<>();
public Builder addConverterFactory(Converter.Factory factory) {
converterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
Copy the code
The method simply adds the incoming data converter factory to the collection of data converter factories in the member variable.
3.4 Adding a network request adapter factory
private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
callAdapterFactories.add(checkNotNull(factory, "factory == null"));
return this;
}
Copy the code
This method also adds the incoming network request adapter factory to the collection of network request adapter factories in the member variable.
3.5 Call the Build method to build Retrofit objects
public Retrofit build() {// Make sure baseUrl is not nullif (baseUrl == null) {
throw new IllegalStateException("Base URL required."); } // Create callFactory by default OkHttpClient okHttp3.call.factory callFactory = this.callFactory;if(callFactory == null) { callFactory = new OkHttpClient(); } / / get the default callback actuators, no will call platform. DefaultCallbackExecutor Executor callbackExecutor = this. CallbackExecutor;if(callbackExecutor == null) { callbackExecutor = platform.defaultCallbackExecutor(); } // Create a network request adapter factory set, Call platform. DefaultCallAdapterFactories request to join the default network adapter Factory List < CallAdapter. Factory > callAdapterFactories = new ArrayList<>(this.callAdapterFactories); callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor)); / / create the data Converter Factory collection List < Converter. The Factory > converterFactories = new ArrayList < > (1 + this. ConverterFactories. The size () + platform.defaultConverterFactoriesSize()); // Add built-in converterFactories. Add (new BuiltInConverters()); / / add the add method to add data converter plant converterFactories. AddAll (enclosing converterFactories); / / add the default data converter plant converterFactories addAll (platform. DefaultConverterFactories ()); // Create a Retrofit object to returnreturn new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
}
Copy the code
A lot of initialization is done in the Build method, including calling the methods seen in the platform adapter Android, and ultimately creating the required Retrofit objects. Let’s look at what it does. The first is to determine if baseUrl is null, which throws an exception, which corresponds to the fact that we have to set baseUrl when we use Retrofit. It then initializes a callFactory network request factory, which is OkHttpClient by default, showing that Retrofit uses the OkHttp framework by default for underlying network requests. And then we create a callback executor and by default we call the corresponding method in the Platform we looked at before to get the main thread executor. The next two steps are essentially the same: create the network request adapter factory set and the data converter factory set and add the default network request adapter factory data converter factory. Finally, the initialization is complete and a Retrofit object is created. Look again at Retrofit’s member variables and constructors.
Public final class Retrofit {// a concurrent ConcurrentHashMap, a cache of request methods, Private final Map<Method, ServiceMethod<? >> serviceMethodCache = new ConcurrentHashMap<>(); // Network request Factory final okHttp3.call.factory callFactory; // baseUrl final HttpUrl baseUrl; // Final List<Converter.Factory> converterFactories; // Final List< callAdapterFactory > callAdapterFactories; // Final @nullable Executor callbackExecutor; // Whether parsing flags are verified in advance Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl, List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories, @Nullable Executor callbackExecutor, boolean validateEagerly) { this.callFactory = callFactory; this.baseUrl = baseUrl; this.converterFactories = converterFactories; // Copy+unmodifiable at call site. this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site. this.callbackExecutor = callbackExecutor; this.validateEagerly = validateEagerly; }... }Copy the code
Here you see that the constructor is just doing the initialization assignment, and if you look at its member variables, in addition to the ones you’ve seen before, there’s a ConcurrentHashMap that caches the ServiceMethod. There is also a Boolean flag called validategbit/s, which is used to check whether parsing is validated ahead of time. For details, see this later in the code.
3.6 Call Retrofit’s create method to create a network request interface object
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return(T) Proxy.newProxyInstance(service.getClassLoader(), new Class<? >[] { service }, newInvocationHandler() {
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 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);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
Copy the code
This approach is at the heart of Retrofit’s design, using the appearance pattern and the dynamic proxy pattern. This method also creates instances of the API interface. This method is a little bit too many steps, step by step. The first thing you see is the validate15 flag, which is used here, and when it’s true it calls the eagerlyValidateMethods method, which goes into the eagerlyValidateMethods method.
private void eagerlyValidateMethods(Class<? > service) { Platform platform = Platform.get();for (Method method : service.getDeclaredMethods()) {
if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
loadServiceMethod(method);
}
}
}
Copy the code
The eagerlyValidateMethods method iterates through all the methods in the network request interface class passed in by the parameter, and loadServiceMethod is called to pass in the method when it determines that it is not a default method and is not static. LoadServiceMethod is a method used to resolve methods in the API interface. Specifically, in the example above, each method in ApiService is resolved into a ServiceMethod object and cached. And we’ll see more about that later, but let’s go back to the create method.
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return(T) Proxy.newProxyInstance(service.getClassLoader(), new Class<? >[] { service }, newInvocationHandler() {
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 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);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
Copy the code
EagerlyValidateMethods defaults to false so it defaults to return (T) proxy.newProxyInstance. A dynamic proxy is used here. The invoke method checks whether the method is an Object method and executes it as it is. Then call platform. IsDefaultMethod judge whether the default method, is just call platform. InvokeDefaultMethod, throw an exception in this method. Return loadServiceMethod(method).invoke(args! = null ? The args: emptyArgs). The loadServiceMethod method would normally be called first, but let’s look at it in more detail.
ServiceMethod<? > loadServiceMethod(Method method) { ServiceMethod<? > result = serviceMethodCache.get(method);if(result ! = null)return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if(result == null) { result = ServiceMethod.parseAnnotations(this, method); serviceMethodCache.put(method, result); }}return result;
}
Copy the code
LoadServiceMethod first looks for this method in the serviceMethodCache and returns it if it finds it. Find enters the synchronized block, here is the DCL singleton pattern, synchronized code block again to determine whether a cache exists in this method, there is no call ServiceMethod. ParseAnnotations annotation method resolution method to obtain analytical results, and adding the results ServiceMethod cache. Next, look at parseAnnotations from ServiceMethod, a method for resolving annotations.
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method Method) {/ / parsing annotations for a RequestFactory RequestFactory RequestFactory = RequestFactory. ParseAnnotations (retrofit, method); // Get the method return Type TypereturnType = method.getGenericReturnType(); // A variable or wildcard in the return type throws an exceptionif (Utils.hasUnresolvableType(returnType)) {
throw methodError(method,
"Method return type must not include a type variable or wildcard: %s".returnType); } // Throw an exception if the return type is nullif (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}
return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}
Copy the code
There are three main things that ServiceMethod does in the parseAnnotations method. One is to call the RequestFactory’s parseAnnotations method to parse the annotations to obtain a RequestFactory object. The other is to get the return type of the method and verify the exception. The third is to continue calling the HttpServiceMethod’s parseAnnotations method. Let’s look at the RequestFactory’s parseAnnotations method.
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
return new Builder(retrofit, method).build();
}
Copy the code
Create a RequestFactory object in constructor mode from the RequestFactory’s parseAnnotations method. First look at the Builder constructor.
Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; // Initialize annotations on methods, corresponding to @get this.methodAnnotations = method.getannotations (); / / initialization method for parameter types, corresponding examples using the method of the key parameters, the type of city String enclosing parameterTypes = method. The getGenericParameterTypes (); / / initialize the annotation on the method parameters, corresponding examples using the method of the key parameters, the @ the Query on the city enclosing parameterAnnotationsArray = method. The getParameterAnnotations (); }Copy the code
The constructor still does the initialization of the property, and continues to look at its build method.
RequestFactory build() {// Loop the annotation on the parsing methodfor(Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } // HTTP request method type null throws an exceptionif (httpMethod == null) {
throw methodError(method, "HTTP method annotation is required (e.g., @GET, @POST, etc.)."); } // Annotation errors throw exceptions, Multipart and FormUrlEncoded must be used on post requests with request bodiesif(! 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)."); }} / / gain method parameter number int parameterCount = parameterAnnotationsArray. Length; ParameterHandlers = new ParameterHandler<? >[parameterCount];for(int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) { parameterHandlers[p] = parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter); } // Check for various usage errors and throw corresponding exceptionsif(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."); } // Create RequestFactory object returnsreturn new RequestFactory(this);
}
Copy the code
The build method does four things. One is to parse annotations on methods for HTTP request method types and other request header information. Parses method arguments, throws various error exceptions, and creates RequestFactory instances to return. Then take a quick look at the notation method.
private void parseMethodAnnotation(Annotation annotation) {
if (annotation instanceof DELETE) {
parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
} else if (annotation instanceof GET) {
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
} else if (annotation instanceof HEAD) {
parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
} else if (annotation instanceof PATCH) {
parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
} else if (annotation instanceof POST) {
parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
} else if (annotation instanceof PUT) {
parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
} else if (annotation instanceof OPTIONS) {
parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
} else if (annotation instanceof HTTP) {
HTTP http = (HTTP) annotation;
parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
} else if (annotation instanceof retrofit2.http.Headers) {
String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
if (headersToParse.length == 0) {
throw methodError(method, "@Headers annotation is empty.");
}
headers = parseHeaders(headersToParse);
} else if (annotation instanceof Multipart) {
if (isFormEncoded) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isMultipart = true;
} else if (annotation instanceof FormUrlEncoded) {
if (isMultipart) {
throw methodError(method, "Only one encoding annotation is allowed.");
}
isFormEncoded = true; }}Copy the code
The main purpose of this method is to determine the types of annotations on the method, including various request types such as GET and POST, or other custom request Headers such as retrofit2.http.Headers, and find the corresponding type of parsing method for further parsing. RequestFactory parses the methods and method parameters we wrote in ApiService and encapsulates them into objects for later use. After looking at the RequestFactory, go back to the parseAnnotations method of ServiceMethod. That method then calls the parseAnnotations method of HttpServiceMethod and keeps tracing.
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
Retrofit retrofit, Method method, RequestFactory requestFactory) {
boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
boolean continuationWantsResponse = false;
boolean continuationBodyNullable = false; Annotations [] Annotations = method.getanannotations (); Type adapterType;if (isKotlinSuspendFunction) {
......
} else{/ / this way the Kotlin method, obtain method return types adapterType = method. The getGenericReturnType (); } // call createCallAdapter to create a CallAdapter. ReturnT> callAdapter = createCallAdapter(retrofit, method, adapterType, annotations); ResponseType = callAdapter.responseType(); // Response in okhttp throws an exceptionif (responseType == okhttp3.Response.class) {
throw methodError(method, "'"
+ getRawType(responseType).getName()
+ "' is not a valid response body type. Did you mean ResponseBody?"); } // The response type does not contain a generic type throwing exceptionif (responseType == Response.class) {
throw methodError(method, "Response must include generic type (e.g., Response<String>)");
}
// TODO support Unit for Kotlin?
if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
throw methodError(method, "HEAD method must use Void as response type."); } // Call createResponseConverter to Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); // Get the request Factory okhttp3.call.factory callFactory = retrofit.callFactory;if(! IsKotlinSuspendFunction) {// Not Kotlin method go here // Create the requestFactory, callFactory, responseConverter, and callAdapter created before CallTestedreturn new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
} else if (continuationWantsResponse) {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return (HttpServiceMethod<ResponseT, ReturnT>) new SuspendForResponse<>(requestFactory,
callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
} else {
//noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
return(HttpServiceMethod<ResponseT, ReturnT>) new SuspendForBody<>(requestFactory, callFactory, responseConverter, (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter, continuationBodyNullable); }}Copy the code
Take a look at the flow of HttpServiceMethod’s parseAnnotations method, which does these things. The createCallAdapter method is called to create a network request adapter, the createResponseConverter method is called to create the response data converter, and the Network request factory callFactory is retrieved from the passed RetroFIT object. Finally, a CallStuck return is created from the above objects. Now let’s take a closer look at these methods first createCallAdapter.
private static <ResponseT, ReturnT> CallAdapter<ResponseT, ReturnT> createCallAdapter(
Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {try {// Call retrofit.calladapterreturn (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); } } public CallAdapter<? ,? > callAdapter(TypereturnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
Copy the code
CreateCallAdapter calls the Retrofit callAdapter method, which in turn calls its nextCallAdapter method and returns a callAdapter object.
public CallAdapter<? ,? > nextCallAdapter(@Nullable CallAdapter.Factory skipPast, TypereturnType,
Annotation[] annotations) {
checkNotNull(returnType, "returnType == null");
checkNotNull(annotations, "annotations == null"); The nextCallAdapter method passes a null skipPast parameter, so indexOf returns -1. Eventually start to 0 int start = callAdapterFactories. IndexOf (skipPast) + 1; // Loop through the collection of network request adapter factoriesfor(int i = start, count = callAdapterFactories.size(); i < count; I++) {// call the Factory get method to get the adapter CallAdapter<? ,? > adapter = callAdapterFactories.get(i).get(returnType, annotations, this); // Return adapter if it is not emptyif(adapter ! = null) {returnadapter; }}... }Copy the code
The nextCallAdapter method iterates through the collection of adapter factories requested from the network, calling the factory’s GET to get the CallAdapter based on the return type of the method. Remember to create default factory collection added a DefaultCallAdapterFactory? The Retrofit default method return type Call corresponds to this factory. Go to its get method and look.
@Override public @Nullable CallAdapter<? ,? > get( TypereturnType, Annotation[] annotations, Retrofit retrofit) {
if (getRawType(returnType) ! = Call.class) {return null;
}
if(! (returnType instanceof ParameterizedType)) {
throw new IllegalArgumentException(
"Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
}
final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
final Executor executor = Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
? null
: callbackExecutor;
returnnew CallAdapter<Object, Call<? >>() { @Override public TyperesponseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
returnexecutor == null ? call : new ExecutorCallbackCall<>(executor, call); }};Copy the code
DefaultCallAdapterFactory the get method of judging according to the incoming method return type, the return type is not the Call type directly returns null. The correct type returns a CallAdapter.
The createCallAdapter method calls the createResponseConverter method. The createCallAdapter method calls the createResponseConverter method.
private static <ResponseT> Converter<ResponseBody, ResponseT> createResponseConverter( Retrofit retrofit, Method method, Type responseType) { Annotation[] annotations = method.getAnnotations(); Try {// Call responseBodyConverter againreturn 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);
}
}
public <T> Converter<ResponseBody, T> responseBodyConverter(Type type[], an Annotation annotations) {/ / and then call nextResponseBodyConverter methodreturn nextResponseBodyConverter(null, type, annotations); Public <T> Converter<ResponseBody; T> nextResponseBodyConverter( @Nullable Converter.Factory skipPast, Typetype, Annotation[] annotations) {
checkNotNull(type."type == null");
checkNotNull(annotations, "annotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
for(int i = start, count = converterFactories.size(); i < count; I++) {// call the data Converter factory responseBodyConverter method to Converter<ResponseBody,? > converter = converterFactories.get(i).responseBodyConverter(type, annotations, this);
if(converter ! = null) { //noinspection uncheckedreturn(Converter<ResponseBody, T>) converter; }}... }Copy the code
The logical flow of the createResponseConverter method is similar to the previous flow for obtaining the network request adapter. The Retrofit class calls the corresponding method, and then the corresponding next method is called to walk through the collection of data converters to get the appropriate converters. Here’s a look at the responseBodyConverter method of the commonly used Gson converter.
public Converter<ResponseBody, ? > responseBodyConverter(Typetype, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type));
return new GsonResponseBodyConverter<>(gson, adapter);
}
Copy the code
See will eventually return a GsonResponseBodyConverter object.
The loadServiceMethod method finally gets a CallStuck object, which then calls its invoke method. Callstuck does not implement the Invoke method. The Invoke method is in its parent class, HttpServiceMethod.
@Override final @Nullable ReturnT invoke(Object[] args) {
Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
return adapt(call, args);
}
Copy the code
The Invoke method creates a Call implemented as OkHttpCall. Note that this Call is not yet the Call class in OkHttp; it is also a class in the Retrofit package.
package retrofit2; . final class OkHttpCall<T> implements Call<T> { private final RequestFactory requestFactory; private final Object[] args; private final okhttp3.Call.Factory callFactory; private final Converter<ResponseBody, T> responseConverter; private volatile boolean canceled; // This rawCall is OkHttp's call@guardedBy ("this") private @Nullable okhttp3.Call rawCall; . }Copy the code
You can see that this class is actually in the RetroFIT2 package, and that this class has a rawCall member variable, which is the Call in the OkHttp package. Go back to the Invoke method and pass the OkHttpCall object into the Adapt method, which calls in CallCaller.
@Override protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
return callAdapter.adapt(call);
}
Copy the code
Method is called again callAdapter adapt method, callAdapter here is found in the adapter factory set in the front, the default is DefaultCallAdapterFactory get method returns.
@Override public @Nullable CallAdapter<? ,? > get( TypereturnType, Annotation[] annotations, Retrofit retrofit) {
......
returnnew CallAdapter<Object, Call<? >>() { @Override public TyperesponseType() {
return responseType;
}
@Override public Call<Object> adapt(Call<Object> call) {
returnexecutor == null ? call : new ExecutorCallbackCall<>(executor, call); }}; }Copy the code
The return method implements the CallAdapter, where the Adapt method returns an ExecutorCallbackCall type that implements Retrofit’s Call interface, which is the final type returned. At this point
3.7 Calling a method in the Network Request Interface object
As you can see from the previous step, the fetching network request interface class actually uses dynamic proxies, so the fetching object is not the object created through the network request interface but its proxy object. So when you call a Method in the interface at this step, for example, using the getWeather Method in the example, the dynamic proxy object intercepts its Invoke Method. The Method argument in the invoke Method is now the invoked Method getWeather, The loadServiceMethod(Method).invoke method is called as described in the previous step, and an ExecutorCallbackCall object is returned. The next step is to make synchronous and asynchronous requests.
3.8 Synchronizing the Request Execute method
The synchronous request method invokes the Execute method, which is available in the Execute method of the ExecutorCallbackCall class.
@Override public Response<T> execute() throws IOException {
return delegate.execute();
}
Copy the code
This delegate is passed in from the constructor. Look at the constructor.
ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
this.callbackExecutor = callbackExecutor;
this.delegate = delegate;
}
Copy the code
The constructor initializes two member variables, a callbackExecutor called by the previous code as the main thread’s callbackExecutor, and a second delegate as the OkHttpCall created in the invoke method. So let’s go back to OkHttpCall and look at its execute method.
@Override public Response<T> execute() throws IOException { okhttp3.Call call; synchronized (this) { ...... call = rawCall; // Call is currently nullif(call == null) {try {// Call createRawCall to create a call call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); // Do not assign a fatal error to creationFailure. creationFailure = e; throw e; }}} // Cancel the call.cancel callif(canceled) { call.cancel(); } // Call call.execute to get the Response from OkHttp and call parseResponse to parse the Responsereturn parseResponse(call.execute());
}
Copy the code
Execute (); createRawCall (OkHttp); createRawCall (OkHttp); Finally, the parseResponse method is called to parse the response and return the result. Here’s a look at the createRawCall method.
private okhttp3.Call createRawCall() throws IOException {
okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}
Copy the code
Requestfactory. create creates a Request from OkHttp, which encapsulates the details of a Request method. The callFactory.newCall method is then called to retrieve the returned Call object. The callFactory here knows from the previous code that the default is an OkHttpClient. So this method essentially executes okHttpClient.newCall (request), which is OkHttp’s base request operation. After receiving the Response returned by OkHttp, the parseResponse method is called to parse the Response into Retrofit’s Response.
Response<T> parseResponse(okHttp3. Response rawResponse) throws IOException {// Get the body ResponseBody rawBody = rawResponse.body(); RawResponse = rawResponse.newBuilder().body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) .build(); Int code = rawResponse.code(); The response code is less than 200 and greater than 300if(code < 200 || code >= 300) { try { // Buffer the entire body to avoid future I/O. ResponseBody bufferedBody = Utils.buffer(rawBody); // Call Response.error to return body and rawResponsereturnResponse.error(bufferedBody, rawResponse); } finally { rawBody.close(); } // The response code is 204 or 205if(code == 204 || code == 205) { rawBody.close(); // If the Response code is 204 or 205, response. success returns an empty Response bodyreturnResponse.success(null, rawResponse); } / / the response body body encapsulated into a ExceptionCatchingResponseBody object ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody); Try {/ / call responseConverter. Convert method to transform the response results T body = responseConverter. Convert (catchingBody); // Calling Response.success returns the converted body and rawResponsereturn Response.success(body, rawResponse);
} catch (RuntimeException e) {
// If the underlying sourcethrew an exception, propagate that rather than indicating it was // a runtime exception. catchingBody.throwIfCaught(); throw e; }}Copy the code
The parseResponse method separates the response body from other response status information and parses the response body separately. The process in this method is to call Response.error(bufferedBody, rawResponse) and return a Response in Retrofit to determine if the Response code is not between 200 and 300 and if there is an exception. A return code of 204 or 205 indicates that the Response is successful but no Response body is returned, so the Response body passed in by the call to Response.success is NULL. Response.success(body, rawResponse) returns the complete Response from Retrofit.
public static <T> Response<T> error(ResponseBody body, okhttp3.Response rawResponse) {
checkNotNull(body, "body == null");
checkNotNull(rawResponse, "rawResponse == null");
if (rawResponse.isSuccessful()) {
throw new IllegalArgumentException("rawResponse should not be successful response");
}
return new Response<>(rawResponse, null, body);
}
public static <T> Response<T> success(@Nullable T body, okhttp3.Response rawResponse) {
checkNotNull(rawResponse, "rawResponse == null");
if(! rawResponse.isSuccessful()) { throw new IllegalArgumentException("rawResponse must be successful response");
}
return new Response<>(rawResponse, body, null);
}
Copy the code
The error and success methods are similar to each other. Both methods first determine whether success is achieved and directly throw an exception if it does not meet expectations. And then you create a corresponding object that you need in Retrofit and you return a Response. The flow of synchronous request methods ends here. And then to watch the data conversion responseConverter. Convert method, here called the data converter convert method in the factory, because usually use Gson parsing is more, So here is obtained from the corresponding factory GsonConverterFactory Gson GsonResponseBodyConverter converter.
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.");
}
returnresult; } finally { value.close(); }}}Copy the code
GsonResponseBodyConverter convert method is simple, is the call of the Gson method. A JsonReader is created from the Gson object passed in the construct and the characters of the response body are passed in. Typeadapter.read (JsonReader) is called to parse the request body into the corresponding entity class.
3.9 Requesting the EnQueue Method asynchronously
Async, as with synchronous, calls the enQueue method corresponding to the ExecutorCallbackCall, which passes in an asynchronous Callback.
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 OkHttps 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(newRunnable() {
@Override public void run() { callback.onFailure(ExecutorCallbackCall.this, t); }}); }}); }Copy the code
The EnQueue method of ExecutorCallbackCall also calls the EnQueue method of OkHttpCall, passing in another Callback. In both the successful onResponse and failed onFailure methods of this callback, callBackExecutor.execute is first called for thread switching. CallbackExecutor is the MainThreadExecutor created at initial initialization, so callbacks to callback.onFailure and callback.onResponse are executed in the main thread, passing the result of the response to the outer layer. The asynchronous invocation process ends at this point. Finally, look at the enqueue method in OkHttpCall.
@Override public void enqueue(final Callback<T> callback) {
checkNotNull(callback, "callback == null"); okhttp3.Call call; Throwable failure; synchronized (this) { ...... call = rawCall; .if(call == null && failure == null) {try {// Create OkHttp call call = rawCall = createRawCall(); } catch (Throwable t) { throwIfFatal(t); failure = creationFailure = t; }... // Cancel the judgmentif(canceled) { call.cancel(); } // Call call.enqueue call.enqueue(new okhttp3).Callback() {@override public void onResponse(okHttp3. Call Call, okhttp3.Response rawResponse) {// Response<T> Response; Response Response = parseResponse(rawResponse); } catch (Throwable e) { throwIfFatal(e); callFailure(e);return; } try {// call callback.onResponse 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) {// Failed callback callFailure(e); } private void callFailure(Throwable e) {try {// Call callback.onFailure callback.onFailure(okHttpCall.this, e); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); // TODO this is not great } } }); }Copy the code
Call enQueue (OkHttp); Call enqueue (OkHttp); Call enqueue (OkHttp); After parsing, callback.onResponse is called, or callback.onFailure is called if OkHttp fails.
4. Design patterns in Retrofit source code
4.1 Builder model
You don’t have to look for this pattern whether it’s in OkHttp or Retrofit. The class Retrofit in Retrofit is the Builder pattern.
The Builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations.
Builder mode has the following characters:
- I’m a Director. Responsible for ordering existing modules and notifying Builder to start building.
- Builder: The abstract Builder class. Components of a canonical product, typically implemented by subclasses.
- ConcreteBuilder: a ConcreteBuilder that implements a method defined by the abstract Builder class and returns a constructed object.
- Product: indicates the Product category.
Ex. :
/** * public class Computer {private String mCPU; private String mMainBoard; private String mRAM; public void setmCPU(String mCPU) { this.mCPU = mCPU; } public void setmMainBoard(String mMainBoard) { this.mMainBoard = mMainBoard; } public void setmRAM(String mRAM) { this.mRAM = mRAM; Public abstract class Builder {public abstract void buildCpu(String CPU); public abstract void buildMainboard(String mainboard); public abstract void buildRam(String ram); public abstract Computer create(); } public class ComputerBuilder extends Builder {private Computer = new Computer(); @Override public void buildCpu(String cpu) { computer.setmCPU(cpu); } @Override public void buildMainboard(String mainboard) { computer.setmMainBoard(mainboard); } @Override public void buildRam(String ram) { computer.setmRAM(ram); } @Override public Computercreate() {
returncomputer; Public class Director {Builder Builder = null; public Director(Builder builder) { this.builder = builder; } public Computer createComputer(String cpu, String mainboard, String ram) { this.builder.buildCpu(cpu); this.builder.buildMainboard(mainboard); this.builder.buildRam(ram);returnbuilder.create(); }}Copy the code
Call:
Builder builder = new ComputerBuilder();
Director director = new Director(builder);
Computer computer = director.createComputer("i7 7700"."Asus M10H"."Samsung 8G DDR4");
Copy the code
Usage Scenarios:
- When algorithms that create complex objects should be independent of the components of the object and how they are assembled.
- The same method is executed in different order. Produces different outcomes of events.
- Multiple part heads can be assembled into an object without producing the same results.
- The product class is very complex, or the order of calls in the product class is different, resulting in different performance.
- When creating some complex objects, the order of construction between the internal components of these objects is stable, but the internal components of objects face complex changes.
Advantages:
Using the Builder pattern allows clients to avoid having to know the details of the product’s internal composition. Concrete constructions of these classes are independent of each other and easy to expand. Since the specific builder is independent, it is possible to refine the construction process without any impact on other modules
Disadvantages:
Generate redundant Build objects and director classes.
4.2 Appearance Mode
The Retrofit class also conforms to the appearance pattern.
The facade pattern requires that the external and internal communication of a subsystem must be carried out through a unified object. This pattern provides a high-level interface that makes subsystems easier to use.
The appearance mode has the following roles:
- Facade: A Facade class that knows which subsystems are responsible for requests and proxies client requests to the appropriate subsystem objects.
- Subsystem: Subsystem class. There can be one or more subsystems. Implement the functions of the subsystem and handle the tasks assigned by the facade class. Note that the subsystem class does not contain references to the facade class.
Ex. :
*/ public class Food {public voidbuy() {
Log.e("msg"."Shopping for ingredients");
}
}
public class Wash {
public void wash() {
Log.e("msg"."Wash the ingredients.");
}
}
public class Cook {
public void cook() {
Log.e("msg"."Start cooking.");
}
}
public class Serve {
public void serve() {
Log.e("msg"."Serve when you're done."); }} /** * public class Chef {private Food Food; private Wash wash; private Cook cook; private Serve serve; publicChef() {
food = new Food();
wash = new Wash();
cook = new Cook();
serve = new Serve();
}
public void work(){ food.buy(); wash.wash(); cook.cook(); serve.serve(); }}Copy the code
Call:
Chef chef = new Chef();
chef.work();
Copy the code
Usage Scenarios:
When building a hierarchical subsystem, use facade patterns to define entry points for each layer of the subsystem. If subsystems are interdependent, you can have them communicate through a facade interface. 2. Subsystems tend to get more and more complex due to the constant refactoring of fireworks, and most patterns are used to produce many small classes that are difficult to use by external users who call them. Facade patterns provide a simple interface to hide the concrete implementation of a subsystem and isolate changes. 3, when maintaining a large legacy systems, can this system has been very difficult to maintain and expand, but because it contains an important function, so the new requirements must rely on it, then you can use the appearance, to design a rough or complex legacy code provides a simple interface, make the new system and the appearance of class interaction, The facade classes are responsible for interacting with legacy code.
Advantages:
Reduce system dependencies, all of which are dependencies on facade classes, independent of subsystems. The implementation of the subsystem is hidden from the user to reduce the coupling of the user to the subsystem. Enhanced security. Methods in a subsystem cannot be accessed unless they are opened in a facade class.
Disadvantages:
Not conforming to the open closed principle, if the business changes, you may need to modify the appearance class directly.
4.3 Singleton Mode
The singleton pattern is used when the ServiceMethod is created in the loadServiceMethod method.
The singleton pattern is used to ensure that a class has only one instance and provides a global access point to it.
The singleton pattern has the following roles:
- Client: indicates the high-level Client.
- Singleton: Singleton class.
Singletons can be written in many different ways:
// Class loading is completed when the initialization, so the class loading is relatively slow, but the speed of obtaining objects is fast, based on the class loading mechanism, avoiding the problem of multi-thread synchronization, but not to achieve the effect of lazy loading, if you have not always used this instance, it will cause memory waste. public class Singleton { private static Singleton instance = new Singleton(); privateSingleton() {
}
public static Singleton getInstance() {
returninstance; }} // lazy (thread unsafe) // initializes at first call, saves resources but is a bit slow at first load, and does not work well in multithreading. public class Singleton { private static Singleton instance ; privateSingleton() {
}
public static Singleton getInstance() {
if (instance==null){
instance=new Singleton();
}
returninstance; }} // it works well in multithreading, but every time you use getInstance, you need to synchronize it unnecessarily, and most of the time you don't need synchronization. public class Singleton { private static Singleton instance ; privateSingleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null){
instance = new Singleton();
}
returninstance; }} // Double check mode (DCL) // This is nulled twice, the first time for unnecessary synchronization, and the second time for Singleton = null. And using volatile affects performance but ensures correctness. // High efficiency of resource utilization, the first load reaction is a little slow, there are certain defects under high concurrency. DCL failure occurs. public class Singleton { private static volatile Singleton instance; privateSingleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if(instance == null) { instance = new Singleton(); }}}returninstance; }} SingletonHolder (SingletonHolder, SingletonHolder, SingletonHolder, SingletonHolder, SingletonHolder, SingletonHolder); It also guarantees uniqueness. Recommended. public class Singleton { privateSingleton() {
}
public static Singleton getInstance() {
returnSingletonHolder.sInstance; } private static class SingletonHolder { private static final Singleton sInstance = new Singleton(); }} // Enumeration singletons // Instance creation of enumerations is thread-safe and singletons in any case. public enum Singleton { INSTANCE; public voiddoSomeThing() {}}Copy the code
Usage Scenarios:
Ensure a scenario where a class has only one object, avoiding creating multiple objects that consume too much resources, or where there should be only one object of a certain type.
Advantages:
1. Because the singleton pattern has only one instance in memory, it reduces the memory cost, especially when an object needs to be frequently created or destroyed, and the performance cannot be optimized during creation or destruction, the singleton pattern has obvious advantages. 2, the singleton mode produces only one instance to reduce the overhead of system performance. When an object produces more resources, it can be solved by directly producing an object when the application is started, and then permanently resident the memory. 3. The singleton mode can avoid multiple resource occupation, for example, a file write operation. Because only one instance exists in memory, simultaneous write operations on the same resource are avoided. 4, singleton mode can set global access points in the system, optimize and share resources access.
Disadvantages:
1, singleton mode generally has no interface, it is very difficult to expand, if you want to expand in addition to modify the code there is basically no second way to achieve. 2. Android singletons are prone to memory leaks if they hold the Context, so pass the Context to the singletons, preferably the Application Context.
4.4 Adapter Mode
Retrofit of the get method returns a CallAdapter DefaultCallAdapterFactory is an adapter pattern.
The adapter pattern transforms the interface of one class into another that the client expects, thus enabling two classes to work together that would otherwise not work together due to interface mismatches.
The adapter pattern has the following roles:
-
Target: The Target role, that is, the desired interface. Note: Since we are talking about the class adapter pattern, the target cannot be a class.
-
Adapee: source role, interface that needs to be adapted now.
-
Adaper: Adapter role. The adapter class is the core of this pattern. The adapter converts the source interface into the target interface. Obviously, this role cannot be an interface, but must be a concrete class.
Example: Class adapter pattern:
Public int getVolt5(); /** * Target: Volt5 */ public int getVolt5(); } /** * Adaotee: 2volt interface */ public class Volt220 {public intgetVolt220() {
return220; * * *}} / Adapter: 220 v voltage into * / public needs 5 v voltage class VoltAdapter implements FiveVolt {private Volt220 Volt220; public VoltAdapter(Volt220 volt220) { this.volt220 = volt220; } @Override public intgetVolt5() {
return5; }}Copy the code
Call:
VoltAdapter adapter = new VoltAdapter(new Volt220());
adapter.getVolt5();
Copy the code
Object adapter pattern:
/** * TestVolt5 (); /** * TestVolt5 (); } /** * Adaotee: 2volt interface */ public class Volt220 {public intgetVolt220() {
return220; /** * public class VoltAdapter string (string, string, string, string, string, string, string, string); public VoltAdapter1(Volt220 adaptee) { this.mVolt220 = adaptee; } public intgetVolt220() {return mVolt220.getVolt220();
}
@Override
public int getVolt5() {
return5; }}Copy the code
Call:
VoltAdapter adapter = new VoltAdapter(new Volt220());
adapter.getVolt5()
Copy the code
Usage Scenarios:
1. The system needs to use the existing class, but the interface of this class does not meet the needs of the system, that is, the interface is incompatible. 2. You want to create a reusable class that works with classes that don’t have much to do with each other, including classes that may be introduced in the future. 3. A unified output interface is required, and the type of input is unpredictable.
Advantages:
Better reusability: The system needs to use existing classes whose interfaces do not meet the needs of the system. The adapter pattern allows for better reuse of these capabilities. Better extensibility: When implementing adapter functionality, you can call your own developed functionality, which naturally extends the functionality of the system.
Disadvantages:
Too much use of adapters will make the system very messy, not easy to grasp as a whole. For example, clearly see is called A interface, in fact, internal adaptation into the IMPLEMENTATION of THE B interface, A system if too much of this situation, is tantamount to A disaster. So if you don’t have to, you can skip the adapter and refactor your system directly.
4.5 Policy Mode
Retrofit is a policy pattern in addCallAdapterFactory.
The strategy pattern defines a set of algorithms, encapsulates each algorithm, and makes them interchangeable. The policy pattern allows an algorithm to change independently of its customers.
The policy mode has the following roles:
- Context: indicates the Context role. The context used to operate policies serves as a link between the preceding and the following, shielding high-level modules from direct access to policies and algorithms.
- Stragety: Abstract policy roles, the abstraction of policies or algorithms, usually interfaces.
- ConcreteStragety: The realization of concrete strategies.
Ex. :
Public Calculate {public double Calculate (double num1,double num2); } /** * public implements Calculate {@override public double Calculate (double num1); double num2) {returnnum1+num2; }} /** * public implements Calculate {@override public double Calculate (double) num1, double num2) {returnnum1-num2; Public class MultiplyCalculate implements Calculate {@override public double Calculate (double) num1, double num2) {returnnum1 * num2; } /** * Context */ public class Context {private Calculate; public Context(Calculate calculate) { this.calculate = calculate; } public double calculate(double num1, double num2) {returncalculate.calculate(num1, num2); }}Copy the code
Call:
Context context;
context = new Context(new AddCalculate());
double addResult = context.calculate(1, 1);
context = new Context(new SubtractCalculate());
double subtractResult = context.calculate(1, 1);
context = new Context(new MultiplyCalculate());
double multiplyResult = context.calculate(1, 1);
Copy the code
Usage Scenarios:
1. Hide the implementation details of specific strategies (algorithms) from customers, completely independent of each other. 2, for the same type of problems in a variety of ways to deal with, only the specific behavior is different. 3. There are many actions defined in a class, and the actions in the class take the form of multiple conditional statements. The policy pattern moves the associated conditional branches into their respective Stragety classes in place of these conditional statements.
Advantages:
Use policy patterns to avoid multiple conditional statements. Multi-conditional statements are difficult to maintain and error-prone. Easy to expand. When adding policies, simply implement the interface.
Disadvantages:
Each policy is a class with low reusability. If there are too many policies, the number of classes increases. The upper module must know which policies are available in order to use them, which violates the Demeter principle.
4.6 Decoration Mode
The ExecutorCallbackCall in Retrofit uses a decorative pattern, where the OkHttpCall actually performs network requests.
Adding additional responsibilities to an object dynamically gives decorator patterns more flexibility than subclassing in terms of adding functionality.
Decorator mode has the following characters:
- Component: Abstract Component. Can be the primitive object that the interface or abstract class is decorated with.
- ConcreteComponent: Component concrete implementation class, concrete object to be decorated.
- Decorator: Abstract Decorator. Extend the functionality of the Component class without knowing that the Decorator exists for Component. There must be a private variable in its property that points to the Decorator abstract component.
- ConcreteDecorator: Concrete implementation class for the decorator.
Ex. :
Public void learn() {public void learn(); } /** * public class XiaoMing extends People {@override public voidlearn() {
Log.e("msg"."Xiao Ming learned Chinese."); } /** * public class extends People {private class extends People; public Teacher(People people) { this.people = people; } @Override public voidlearn() { people.learn(); Public MathTeacher extends Teacher{public MathTeacher(People) {super(People); } public voidteachMath(){
Log.e("msg"."A math teacher teaches math.");
Log.e("msg"."Xiao Ming learned math.");
}
@Override
public void learn() { super.learn(); teachMath(); }}Copy the code
Call:
XiaoMing xiaoMing = new XiaoMing();
MathTeacher mathTeacher = new MathTeacher(xiaoMing);
mathTeacher.learn();
Copy the code
Usage Scenarios:
Add responsibilities to a single object dynamically and transparently without affecting other objects. 2. Add functionality to an object dynamically. These functions can be undone dynamically. 3. When the system cannot be extended by inheritance or inheritance is not conducive to system maintenance.
Advantages:
Dynamically extend the functionality of an object by combining non-inherited methods, selecting different decorators at run time to achieve different behaviors. Effectively avoid the use of inheritance to extend the function of an object and bring poor flexibility, subclass unlimited expansion of the problem. Concrete component classes and concrete decorator classes can vary independently. Users can add new specific component classes and specific decoration classes according to their needs, which can be combined when used. The original code does not need to be changed, which conforms to the open and closed principle.
Disadvantages:
Because all objects inherit from Component, changes within Component will inevitably affect all subclasses, and changes in the base class will inevitably affect the object’s interior. It is more flexible than inheritance, but also means that decoration mode is more prone to error than inheritance, and it is very difficult to troubleshoot. For objects that have been decorated for many times, it needs to be checked step by step, which is tedious. So use decoration mode only when necessary. The number of decorative layers should not be too many, otherwise it will affect the efficiency.
4.7 Simple Factory Mode
The Platform class in Retrofit is the simple factory pattern.
The simple factory pattern, also known as the static Factory method pattern, is where a factory object decides which instance of a product class to create.
The simple Factory mode has the following roles:
- Factory: The Factory class, the core of the simple Factory pattern, is responsible for creating the internal logic for all instances.
- IProduct: Abstract product class. This is the parent class of all objects created by the simple factory pattern and is responsible for describing the common interface shared by all instances.
- Product: indicates a specific Product.
Ex. :
/** * public abstract class MobilePhone {/** * public abstract void systemVersion(); } /** * public class extends MobilePhone {@override public voidsystemVersion() {
Log.d("msg"."IOS 11");
}
}
public class SamsungNote9 extends MobilePhone {
@Override
public void systemVersion() {
Log.d("msg"."Android 8.0"); Public class MobilePhoneFactory {public static MobilePhone createMobilePhone(String name) { MobilePhone mobilePhone = null; switch (name) {case "IPhone":
mobilePhone = new IPhoneX();
break;
case "Samsung":
mobilePhone = new SamsungNote9();
break;
default:
break;
}
returnmobilePhone; }}Copy the code
Call:
MobilePhoneFactory.createMobilePhone("IPhone").systemVersion();
Copy the code
Usage Scenarios:
The factory class is responsible for creating fewer objects. The customer only needs to know the parameters passed into the factory, not the logic of creating the object.
Advantages:
The user obtains the instance of the corresponding class according to the parameters, and does not care about the object creation logic. The coupling is reduced.
Disadvantages:
If a new type is needed, the factory needs to be modified. This violates the open and closed principle. Simple factories need to know the types of all generated classes, and are not suitable when there are too many subclasses or too many subclass levels.
4.8 Abstract factory mode
The Converter in Retrofit is the abstract factory pattern.
Define an interface for creating objects and let subclasses decide which class to instantiate. The factory method delays the instantiation of a class to its subclasses.
The factory method pattern has the following roles:
- Product: Abstract Product class.
- ConcreteProduct: ConcreteProduct class. Implement the Product interface.
- Factory: Abstract Factory class. Return a Product object.
- ConcreteFactory: ConcreteFactory class. Returns the ConcreteProduct instance.
Ex. :
Public abstract class Computer {public abstract void systemVersion(); } /** * public class extends Computer {@override public voidsystemVersion() {
Log.d("msg"."MAC OS");
}
}
public class LenovoComputer extends Computer {
@Override
public void systemVersion() {
Log.d("msg"."Windows10"); }} public abstract class ComputerFactory {public abstract <T extends Computer> T createComputer(Class<T> clz); Public class ComputerFactoryImpl extends ComputerFactory {@override public <T extends Computer> T createComputer(Class<T> clz) { Computer computer = null; String clzName = clz.getName(); Computer = (computer) class.forName (clzName).newinstance (); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }return(T) computer; }}Copy the code
Call:
ComputerFactory computerFactory = new ComputerFactoryImpl();
LenovoComputer computer = computerFactory.createComputer(LenovoComputer.class);
computer.systemVersion();
Copy the code
Usage Scenarios:
A family of objects with the same constraints can use the abstract factory pattern.
Advantages:
Separation of interface and implementation, the client using the abstract factory need to create objects, but the client didn’t know who’s concrete implementation, the client just product oriented interface programming, make its decoupled from the specific product realization, based on the interface and implementation of separation at the same time, make the abstract the factory method pattern in class more flexible and easy to switch products.
Disadvantages:
One is the explosive increase in class files, and the other is less easy to extend new product classes.
4.9 Static Proxy Mode
The ExecutorCallbackCall class in Retrofit is a static proxy pattern that proxies OkHttpCall.
The proxy pattern provides a proxy for other objects to control access to that object.
The proxy mode has the following roles:
- Subject: Abstracts the topic class, declaring and representing the interface methods common to the topic and proxy classes.
- RealSubject: RealSubject class. The real subject represented by the proxy class.
- Proxy: indicates the Proxy class. Holds a reference to a real topic and calls the corresponding interface method in the real topic class to execute in the interface method it implements.
Ex. :
/** * public interface Person {String name (String name); String dance(String name); } public class ZhangYiXing implements Person {@override public String name (String name) {return "Zhang Yixing sang a song"+name;
}
@Override
public String dance(String name) {
return "Zhang Yixing danced one"+name; } /** * public class implements Person {private Person zhangYiXing; public JingJiRenProxy(Person zhangYiXing) {if (zhangYiXing instanceof ZhangYiXing) {
this.zhangYiXing = zhangYiXing;
}
}
@Override
public String sing(String name) {
String money = "I'm an agent. I need 1 million yuan to sing for Zhang Yixing. I'll pay 500,000 yuan in advance.";
String sing = zhangYiXing.sing(name);
String balance = "\n Balance paid 500,000";
return money + sing + balance;
}
@Override
public String dance(String name) {
String money = "I'm an agent. I need 2 million yuan to dance with Zhang Yixing. I'll pay 1 million yuan in advance.";
String dance = zhangYiXing.dance(name);
String balance = "\n Balance paid 1 million";
returnmoney + dance + balance; }}Copy the code
Call:
JingJiRenProxy jingJiRenProxy = new JingJiRenProxy(zhangyixi);
String sing1 = jingJiRenProxy.sing("Dreams never rain.");
String dance1 = jingJiRenProxy.dance(cygnets);
Copy the code
Usage Scenarios:
When it is difficult to access an object directly or not, a proxy object can be used to introduce access. To ensure transparency of the client, the proxy object and the proxy object need to implement the same interface.
Advantages:
Decouple the agent from the principal. The proxy object acts as an intermediary between the client and the target object to protect the target object.
Disadvantages:
Additions to classes.
4.10 Dynamic Proxy Mode
The dynamic proxy pattern is used in Retrofit’s retrofit.create method.
Dynamic proxies generate proxy objects dynamically through reflection, meaning that we do not need to know who to proxy at all during the coding phase, and who to proxy will be determined during the execution phase.
Ex. :
/** * Proxy class */ public class superclass {private Person zhangYiXing; public SuperJingJiRenProxy(Person zhangYiXing) {if (zhangYiXing instanceof ZhangYiXing) {
this.zhangYiXing = zhangYiXing;
}
}
public Person getProxy() {
return (Person) Proxy.newProxyInstance(ZhangYiXing.class.getClassLoader(), zhangYiXing.getClass().getInterfaces(), new InvocationHandler() {/** * @param proxy passes in the proxy object itself * @param method passes in the method currently called by the proxy object * @param args passes in the method parameters * @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("sing")) {
String money = "I'm an agent. I need 1 million yuan to sing for Zhang Yixing. I'll pay 500,000 yuan in advance.";
String sing = (String) method.invoke(zhangYiXing, args);
String balance = "\n Balance paid 500,000";
return money + sing + balance;
}
if (method.getName().equals("dance")) {
String money = "I'm an agent. I need 2 million yuan to dance with Zhang Yixing. I'll pay 1 million yuan in advance.";
String dance = (String) method.invoke(zhangYiXing, args);
String balance = "\n Balance paid 1 million";
return money + dance + balance;
}
returnnull; }}); }}Copy the code
Call:
SuperJingJiRenProxy superJingJiRenProxy = new SuperJingJiRenProxy(zhangyixi);
Person proxy = superJingJiRenProxy.getProxy();
String sing2 = proxy.sing("Wait till you're finished.");
String dance2 = proxy.dance("Best stage");
Copy the code
5. To summarize
So that’s a summary of Retrofit. After all, Retrofit is now an essential part of the mainstream MVP+Retrofit+RxJava framework and is very useful. The combination of design patterns used in Retrofit was so subtle that it was necessary to learn in depth, which helped us with both our coding and architectural abilities.
The resources
Android advanced light Android source code design mode analysis and combat