• Retrofit is currently the best network encapsulation framework for Android and encapsulates the OkHttp network request library
  • App applications request through the Retrofit network, essentially encapsulating the request parameters using the Retrofit interface layer, and then OkHttp does the subsequent request operations; When the server data is returned, OkHttp sends the original results to Retrofit, which parses them according to user requirements;

use

Simple to use

Add the dependent
  • OkHttp is built into Retrofit2, so there is no need to add a separate OkHttp dependency
Implementation 'com. Squareup. Retrofit2: retrofit: 2.9.0'Copy the code

Create a Retrofit instance

val baseUrl = "https://api.github.com/"
val okHttpClient = OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(60, TimeUnit.SECONDS)
    .writeTimeout(90, TimeUnit.SECONDS)
    .build()
val retrofit = Retrofit.Builder()
    .baseUrl(baseUrl)
    .client(okHttpClient)
    .build()
Copy the code

Create a class that returns data

class RepoList {
    @SerializedName("items") val items:List<Repo> = emptyList()
}

data class Repo(
    @SerializedName("id") val id: Int,
    @SerializedName("name") val name: String,
    @SerializedName("description") val description: String,
    @SerializedName("stargazers_count") val starCount: String,
)
Copy the code

Create the network request interface

interface ApiService { @GET("search/repositories? sort=stars&q=Android") fun searRepos(@Query("page") page: Int, @Query("per_page") perPage: Int): Call<RepoList> }Copy the code

Create a network request interface instance

val apiService = retrofit.create(ApiService::class.java)
Copy the code

Call the interface instance method to get the Call

val call = apiService.searRepos(1, 5)
Copy the code

Sending network Requests

1. Synchronize the request
val response: Response<RepoList> = call.execute()
if (response.isSuccessful) {
    val repo = response.body()
    LjyLogUtil.d(repo.toString())
} else {
    LjyLogUtil.d("code=${response.code()}, msg=${response.message()}")
    LjyLogUtil.d(IOException("Unexpected code $response").message)
}
Copy the code
2. Asynchronous request
call.enqueue(object : Callback<RepoList> { override fun onResponse(call: Call<RepoList>, result: Response<RepoList>) { if (result.body() ! = null) { val repoList: RepoList = result.body()!! for (it in repoList.items) { LjyLogUtil.d("${it.name}_${it.starCount}") LjyLogUtil.d(it.description) } } } override fun onFailure(call: Call<RepoList>, t: Throwable) { LjyLogUtil.d("onFailure:${t.message}") } })Copy the code

Annotation type

1. Network request method

@get, @post, @PUT, @delete, @head, @Patch, @options
  • They correspond to the network request modes in HTTP
  • The value attribute of the annotation is used to set the relative/full URL, which overrides the baseUrl at the time the Retrofit instance was created
  • Retrofit divides the URL of a web request into two Settings, one is the baseUrl set when the Retrofit instance is created, and the other is the value set or @URL set in the web request method annotation.
@GET("api/items")
fun getRepos(): Call<RepoList>

@GET("https://api.github.com/api/items")
fun getRepos(): Call<RepoList>
Copy the code
@HTTP
  • Replace the role of the above annotations and more functional extensions
/** * method: network request method (case sensitive) * path: network request address path * hasBody: */ @http (method = "GET", hasBody = false) fun getRepos(@url Url: String): Call<RepoList> @HTTP(method = "GET", path = "api/items/{userId}", hasBody = false) fun getRepos2(@Path("userId") userId: String) : Call<RepoList>Copy the code

2. Mark

@FormUrlEncoded
  • Indicates that the RequestBody is the Form Form
@FormUrlEncoded
@POST("api/search")
fun searchRepo( @Field("name") repoName:String): Call<RepoList>
Copy the code
@Multipart
  • Indicates that the request body is a Form that supports file uploading
  • See the @Part section below for details
@Streaming
  • Indicates that the returned data is returned as a stream. This is suitable for scenarios where large amounts of data are returned (if this annotation is not used, the default is to load all data into memory).
@Streaming @GET fun downloadFile(@Url url: String?) : Call<ResponseBody>Copy the code

3. Network request parameters

@Path
  • Default VALUE of the URL address
@GET("api/items/{userId}/repos") fun getItem(@Path("userId") userId: String): Call<Repo> // When the request is initiated, {userId} is replaced with the method parameter userId (annotated by @path).Copy the code
@Url
  • Passing a request URL directly is similar to @get, @post, etc., but passing a parameter is more flexible
@FormUrlEncoded
@POST("api/search")
fun searchRepo(@Url url: String, @Field("name") repoName: String): Call<RepoList>
Copy the code
@Header & @Headers
  • Usage scenario: @header is used to add an unfixed request Header, and @headers is used to add a fixed request Header
  • @header applies to method arguments; @Headers is acting on the method
@Streaming @GET fun downloadFile(@Header("RANGE") start:String , @Url url: String?) : Call<ResponseBody> @Headers("Content-Type: application/json; charset=UTF-8") @POST("api/search") fun searchRepo2(@Body params: Map<String, Any>): Call<RepoList>Copy the code
  • Adding headers can also be done through the okHttp interceptor described in the previous article
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(object : Interceptor{
        override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
            val request=chain.request().newBuilder()
                .addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
                .addHeader("Accept-Encoding", "gzip, deflate")
                .addHeader("Connection", "keep-alive")
                .addHeader("Accept", "*/*")
                .addHeader("Cookie", "add cookies here")
                .build()
            return chain.proceed(request)
        }
    })
    .build()
Copy the code
@Query & @QueryMap
  • Query parameters for the @get method
@GET("search/repositories? sort=stars&q=Android") fun searRepos(@Query("page") page: Int, @Query("per_page") perPage: Int): Call<RepoList> @GET("search/repositories? sort=stars&q=Android") fun searRepos(@QueryMap params: Map<String, Any>): Call<RepoList>Copy the code
@Field & @FieldMap
  • Submit the request form field when sending a Post request, used in conjunction with the @formurlencoded annotation
@FormUrlEncoded
@POST("api/search")
fun searchRepo(@Url url: String, @Field("name") repoName: String): Call<RepoList>

@FormUrlEncoded
@POST("api/search")
fun searchRepo(@Url url: String, @FieldMap params: Map<String, Any>): Call<RepoList>
Copy the code
@Part & @PartMap
  • The form field that submits the request when a Post request is sent, used with the @multipart annotation
  • Difference with @field: It has the same function, but carries more parameter types, including data flow. Therefore, it is suitable for file upload scenarios
@POST("upload/imgFile") @Multipart fun uploadImgFile( @Part("userId") userId: RequestBody? , @PartMap partMap: Map<String, RequestBody? >, @Part("file") file: MultipartBody.Part ): Call<ResponseBody> @Multipart @POST("upload/files") fun uploadFiles( @Part("userId") userId: RequestBody? , @part files: List< multipartBody. Part>): Call<ResponseBody> RequestBody = RequestBody.create(MediaType.parse("multipart/form-data"), "1111") val paramsMap: MutableMap<String, RequestBody> = HashMap() paramsMap["userId"] = RequestBody.create(MediaType.parse("text/plain"), "123456") paramsMap["userName"] = RequestBody.create(MediaType.parse("text/plain"), "jinYang") paramsMap["taskName"] = RequestBody.create(MediaType.parse("text/plain"), ImgFile =File(externalCacheDir, "ljy.jpg") val requestFile: RequestBody = RequestBody.create(MediaType.parse("multipart/form-data"),imgFile ) val partFile = MultipartBody.Part.createFormData("imageUrl", imgFile.name, requestFile) apiService.uploadImgFile(userId,paramsMap,partFile) val imgFile1=File(externalCacheDir, "ljy1.jpg") val imgFile2=File(externalCacheDir, "ljy2.jpg") val imgFile3=File(externalCacheDir, "ljy3.jpg") val imageFiles= arrayOf(imgFile1,imgFile2,imgFile3) val parts = ArrayList<MultipartBody.Part>(imageFiles.size) for (i in imageFiles.indices) { val file: File = imageFiles[i] parts[i] = MultipartBody.Part.createFormData( "file_$i", file.name, RequestBody.create( MediaType.parse("image/*"), file ) ) } apiService.uploadFiles(userId,parts)Copy the code
@Body
  • The custom data type is passed to the server via Post, which is equivalent to @field if it is a Map
@Headers("Content-Type: application/json; charset=UTF-8") @POST("api/add") fun addRepo(@Body repo: Repo): Call<Boolean> @Headers("Content-Type: application/json; charset=UTF-8") @POST("api/add") fun addRepo2(@Body params: Map<String, Any>): Call<Boolean> @Headers("Content-Type: application/json; charset=UTF-8") @POST("api/add") fun addRepo3(@Body body: RequestBody): Call<Boolean> @FormUrlEncoded @POST("api/add") fun addRepo4(@Body body: FormBody): Call<Boolean> // use: val repo = repo (1, "name", "info", "20") apiService. MutableMap<String, Any> = HashMap() map["key"] = "value" apiService.addRepo2(map) val body: RequestBody = RequestBody .create(MediaType.parse("application/json; charset=utf-8"), repo.toString()) apiService.addRepo3(body) val formBody = FormBody.Builder() .add("key", "value") .build() apiService.addRepo4(formBody)Copy the code

Data parser & Request adapter

  • Retrofit supports a variety of data parsing methods and network request adapters that require adding dependencies to Gradle

Data parser

  • Retrofit only supports converting the HTTP ResponseBody to a Call by default. With this Converter, you can replace the ResponseBody with another type.

For example, the commonly used GsonConverterFactory is listed below.

1. Add dependencies
Implementation 'com. Squareup. Retrofit2: converter - gson: 2.9.0' / / gson support [common] [optional] implementation 'com. Squareup. Retrofit2: converter - simplexml: 2.9.0'/support/simplexml [optional] implementation 'com. Squareup. Retrofit2: converter - Jackson: 2.9.0' / / support Jackson (optional) implementation 'com. Squareup. Retrofit2: converter - protobuf: 2.9.0'/support/protobuf [optional] implementation 'com. Squareup. Retrofit2: converter - moshi: 2.9.0'/support/moshi [optional] implementation 'com. Squareup. Retrofit2: converter - wire: 2.9.0' / / wire support [optional] implementation 'com. Squareup. Retrofit2: converter - scalars: 2.9.0' support / / String (optional)Copy the code
2. Added when creating a Retrofit instance
val retrofit = Retrofit.Builder()
    .baseUrl(baseUrl)
    .addConverterFactory(GsonConverterFactory.create())
    .addConverterFactory(JacksonConverterFactory.create())
    .addConverterFactory(SimpleXmlConverterFactory.create())
    .addConverterFactory(ProtoConverterFactory.create())
    .addConverterFactory(ScalarsConverterFactory.create())
    .build()
Copy the code

3. Customize Converter

  • Before customizing, let’s take a look at how the official implementation is implemented. Take GsonConverterFactory as an example
Factory public final class GsonConverterFactory extends Converter.Factory {// Static create method public static GsonConverterFactory create() { return create(new Gson()); } public static GsonConverterFactory create(Gson gson) { if (gson == null) throw new NullPointerException("gson == null"); return new GsonConverterFactory(gson); } private final Gson gson; private GsonConverterFactory(Gson gson) { this.gson = gson; } / / rewrite responseBodyConverter method, the response body to GsonResponseBodyConverter @ Override public Converter < ResponseBody,? > responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); } // Override requestBodyConverter @override public requestBodyConverter <? , RequestBody> requestBodyConverter( Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); }} // Process the response body to Converter, To implement the Converter interface < T > ResponseBody, final class GsonResponseBodyConverter < T > implements the 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 the convert method, Override public T convert(ResponseBody value) throws IOException {JsonReader JsonReader  = gson.newJsonReader(value.charStream()); try { T result = adapter.read(jsonReader); if (jsonReader.peek() ! = JsonToken.END_DOCUMENT) { throw new JsonIOException("JSON document was not fully consumed."); } return result; } finally { value.close(); }}} // Process the request body to Converter, Final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> { private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8"); private static final Charset UTF_8 = Charset.forName("UTF-8"); private final Gson gson; private final TypeAdapter<T> adapter; GsonRequestBodyConverter(Gson gson, TypeAdapter<T> adapter) { this.gson = gson; this.adapter = adapter; } // Override the convert method, Override public RequestBody Convert (T value) throws IOException {Buffer Buffer = new Buffer(); Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); JsonWriter jsonWriter = gson.newJsonWriter(writer); adapter.write(jsonWriter, value); jsonWriter.close(); return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); }}Copy the code
  • So let’s try it for ourselves:
  • Example 1: Return the Call format
//1. Define StringConverter class StringConverter: Converter<ResponseBody, String> { companion object { val INSTANCE = StringConverter() } @Throws(IOException::class) override fun convert(value: ResponseBody): String { return value.string() } } //2. Customise StringConverterFactory to register a StringConverter class with Retrofit: Converter.Factory() { companion object { private val INSTANCE = StringConverterFactory() fun create(): StringConverterFactory {return INSTANCE}} // only convert ResponseBody to String, Override Fun responseBodyConverter(Type: type, Annotations: Array<Annotation? >? , retrofit: Retrofit? ) : Converter<ResponseBody, *>? {return if (type === String::class.java) {StringConverter.INSTANCE} else null}} 3. Use val Retrofit = retrofit.builder ().baseurl (baseUrl) // Custom Converter must be placed before the official supplied Converter //addConverterFactory is sequential, multiple Converters all support the same type, Only by using the first addConverterFactory (StringConverterFactory. Create ()). AddConverterFactory (GsonConverterFactory. The create ()) .build()Copy the code
  • Example 2: ResponseBody is converted to Map
class MapConverterFactory : Converter.Factory() {
    companion object {
        fun create(): MapConverterFactory {
            return MapConverterFactory()
        }
    }

    override fun responseBodyConverter(
        type: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *> {
        return MapConverter()
    }

    class MapConverter : Converter<ResponseBody, Map<String, String>> {
        @Throws(IOException::class)
        override fun convert(body: ResponseBody): Map<String, String> {
            val map: MutableMap<String, String> = HashMap()
            val content = body.string()
            val keyValues = content.split("&").toTypedArray()
            for (i in keyValues.indices) {
                val keyValue = keyValues[i]
                val splitIndex = keyValue.indexOf("=")
                val key = keyValue.substring(0, splitIndex)
                val value = keyValue.substring(splitIndex + 1, keyValue.length)
                map[key] = value
            }
            return map
        }
    }
}
Copy the code

Request adapter

  • Converter is used to convert T in Call, while CallAdapter can convert Call. The following lists the official CallAdapter provided to us.
1. Add dependencies
Implementation 'com. Squareup. Retrofit2: adapter - rxjava2:2.9.0' / / RxJava support [common] [optional] implementation 'com. Squareup. Retrofit2: adapter - java8:2.9.0' / / java8 support [optional] implementation 'com. Squareup. Retrofit2: adapter - guava: 2.9.0' / / guava support (optional)Copy the code
2. Added when creating a Retrofit instance
val retrofit = Retrofit.Builder()
    .baseUrl(baseUrl)
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addCallAdapterFactory(Java8CallAdapterFactory.create())
    .addCallAdapterFactory(GuavaCallAdapterFactory.create())
    .build()
Copy the code
3. Customize the CallAdapter
  • Also let’s take a look at the official RxJava2CallAdapterFactory is how to do
// Adapter factory class, Inheritance CallAdapter. Factory public final class RxJava2CallAdapterFactory extends CallAdapter. Factory {/ / public static the create method static RxJava2CallAdapterFactory create() { return new RxJava2CallAdapterFactory(null, false); }... // Private final @nullable scheduler; private final boolean isAsync; / / constructor private RxJava2CallAdapterFactory (@ Nullable Scheduler Scheduler, Boolean isAsync) {enclosing the Scheduler = Scheduler; this.isAsync = isAsync; } RxJava2CallAdapter @override public @nullable CallAdapter<? ,? > get( Type returnType, Annotation[] annotations, Retrofit retrofit) { Class<? > rawType = getRawType(returnType); . return new RxJava2CallAdapter( responseType, scheduler, isAsync, isResult, isBody, isFlowable, isSingle, isMaybe, false); }} // Adapter class, Final class RxJava2CallAdapter<R> implements CallAdapter<R, Object> {private final Type responseType; RxJava2CallAdapter(Type responseType,....) {this.responseType = responseType; . }... Override public responseType() {return responseType; } // Rewrite the adapt method, Override public Object adapt(Call<R> Call) {Observable<Response<R> responseObservable =  isAsync ? new CallEnqueueObservable<>(call) : new CallExecuteObservable<>(call); Observable<? > observable; if (isResult) { observable = new ResultObservable<>(responseObservable); } else if (isBody) { observable = new BodyObservable<>(responseObservable); } else { observable = responseObservable; } if (scheduler ! = null) { observable = observable.subscribeOn(scheduler); } if (isFlowable) { return observable.toFlowable(BackpressureStrategy.LATEST); } if (isSingle) { return observable.singleOrError(); } if (isMaybe) { return observable.singleElement(); } if (isCompletable) { return observable.ignoreElements(); } return RxJavaPlugins.onAssembly(observable); }}Copy the code
  • Then we’ll try and get one of our own
//1. Define Call class LjyCall<T>(private val Call: Call<T>) {@throws (IOException::class) fun get(): T? { return call.execute().body() } } //2. Class LjyCallAdapter<R>(private val responseType: Type) : CallAdapter<R, Any> { override fun responseType(): Type { return responseType } override fun adapt(call: Call<R>): Any { return LjyCall(call) } } //3. CallAdapterFactory class LjyCallAdapterFactory: CallAdapter.Factory() { companion object { private val INSTANCE = LjyCallAdapterFactory() fun create(): LjyCallAdapterFactory { return INSTANCE } } override fun get( returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit ): CallAdapter<R, Any>? Var rawType = getRawType(returnType) if (rawType == LjyCall::class.java && returnType is ParameterizedType) { val callReturnType = getParameterUpperBound(0, returnType) return LjyCallAdapter(callReturnType) } return null } } //4. Val retrofit = retrofit.builder ().baseurl (baseUrl) Have order addCallAdapterFactory (LjyCallAdapterFactory. The create ()). AddCallAdapterFactory (RxJava2CallAdapterFactory. The create ()) .build()Copy the code

The source code parsing

  • Source address: Square/Retrofit

Retrofit & Builder

The construction method of Builder

  • Retrofit instances are created using the Builder pattern, so let’s look at how the Builder is constructed
Public static final Class Builder {private final Platform Platform; Private @nullable okHttp3.call.factory callFactory; // HttpUrl baseUrl; // HttpUrl baseUrl; Private Final List< ConverterFactory > converterFactories = new ArrayList<>(); Private Final List< callAdapterFactory > callAdapterFactories = new ArrayList<>(); MainThreadExecutor private @Nullable Executor callbackExecutor; private boolean validateEagerly; public Builder() { this(Platform.get()); } Builder(Platform platform) { this.platform = platform; }... }Copy the code
  • Get (); platform.get (); platform.get ()
class Platform {
  private static final Platform PLATFORM = findPlatform();

  static Platform get() {
    return PLATFORM;
  }

  private static Platform findPlatform() {
    return "Dalvik".equals(System.getProperty("java.vm.name"))
        ? new Android() //
        : new Platform(true);
  }
  ...
}
Copy the code
  • Obviously what we need is an Android implementation, whose defaultCallbackExecutor returns MainThreadExecutor that encapsulates the Handler,

What it does is switch from the worker thread to the UI thread

static final class Android extends Platform { Android() { super(Build.VERSION.SDK_INT >= 24); } @Override public Executor defaultCallbackExecutor() { return new MainThreadExecutor(); }... static final class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r); }}}Copy the code

Builder.build()

  • Building a Retrofit instance ends with a call to Build, so let’s look at the implementation of this method
If (baseUrl == null) {throw new IllegalStateException("Base URL required."); } //callFactory defaults to OkHttpClient okHttp3.call.factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); } Executor callbackExecutor = this.callbackExecutor; If (callbackExecutor == null) {// If (callbackExecutor == null) {// If (callbackExecutor == null) {  callbackExecutor = platform.defaultCallbackExecutor(); } // Set of data parsers:  // Make a defensive copy of the adapters and add the default Call adapter. List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories); callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor)); // Set of adapters:  // Make a defensive copy of the converters. List<Converter.Factory> converterFactories = new ArrayList<>( 1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize()); // Add the built-in converter factory first. This prevents overriding its behavior but also // ensures correct behavior when using converters that consume all types. converterFactories.add(new BuiltInConverters()); converterFactories.addAll(this.converterFactories); converterFactories.addAll(platform.defaultConverterFactories()); return new Retrofit( callFactory, baseUrl, unmodifiableList(converterFactories), unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly); }Copy the code
  • As you can see above, baseUrl must be set, so let’s see what it requires
public Builder baseUrl(String baseUrl) { Objects.requireNonNull(baseUrl, "baseUrl == null"); return baseUrl(HttpUrl.get(baseUrl)); } public Builder baseUrl(HttpUrl baseUrl) { Objects.requireNonNull(baseUrl, "baseUrl == null"); List<String> pathSegments = baseUrl.pathSegments(); // If host is followed by path, it must end with '/' if (!" ".equals(pathSegments.get(pathSegments.size() - 1))) { throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl); } this.baseUrl = baseUrl; return this; } /** * Returns a list of path segments like {@code ["a", "b", "c"]} for the URL {@code * http://host/a/b/c}. This list is never empty though it may contain a single empty string. * *  <p><table summary=""> * <tr><th>URL</th><th>{@code pathSegments()}</th></tr> * <tr><td>{@code http://host/}</td><td>{@code [""]}</td></tr> * <tr><td>{@code http://host/a/b/c"}</td><td>{@code ["a", "b", "c"]}</td></tr> * <tr><td>{@code http://host/a/b%20c/d"}</td><td>{@code ["a", "b c", "d"]}</td></tr> * </table> */ public List<String> pathSegments() { return pathSegments; }Copy the code
  • As mentioned in the build method above, callFactory uses OkHttpClient by default, which may not be named as a type.

But if we look at the OkHttpClient source, we’ll see that it implements call.factory;

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
    ...
}
Copy the code

Constructor of Retrofit

  • After looking at Builder, let’s take a look at Retrofit, its variables and constructors
Private Final Map<Method, ServiceMethod<? >> serviceMethodCache = new ConcurrentHashMap<>(); // Final okHttp3.call.factory callFactory; final HttpUrl baseUrl; final List<Converter.Factory> converterFactories; final List<CallAdapter.Factory> callAdapterFactories; final @Nullable Executor callbackExecutor; final boolean validateEagerly; 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

Retrofit’s Create method

  • After we create a Retrofit instance, we call its create method to generate a dynamic proxy object for the interface, as shown below
public <T> T create(final Class<T> service) { validateServiceInterface(service); Return (T) // create an instance of the interface through the dynamic Proxy proxy.newProxyInstance (// parameter 1: classLoader service.getclassLoader (), // Parameter 2: array of interface types new Class<? >[] {service}, // parameter 3: New InvocationHandler() {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); } args = args ! = null ? args : emptyArgs; return platform.isDefaultMethod(method) ? platform.invokeDefaultMethod(method, service, proxy, args) : loadServiceMethod(method).invoke(args); }}); }Copy the code
  • The invoke method finally calls the loadServiceMethod method, which is parsed to ServiceMethod and cached in The serviceMethodCache
ServiceMethod<? > loadServiceMethod(Method method) { ServiceMethod<? > result = serviceMethodCache.get(method); if (result ! = null) return result; Synchronized (serviceMethodCache) {// Obtain result = servicemethodcache.get (method); If (result = = null) {/ / take less than one and join the new cache result = ServiceMethod. ParseAnnotations (this, method); serviceMethodCache.put(method, result); } } return result; }Copy the code
  • Actual code above analytical work is called ServiceMethod. ParseAnnotations, through RequestFactory finish on the interpretation of annotation
abstract class ServiceMethod<T> { static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) { RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method); Type returnType = method.getGenericReturnType(); . return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory); } + abstract @Nullable T invoke(Object[] args); }Copy the code
  • So take a look at RequestFactory. ParseAnnotations dry a little bit of what
final class RequestFactory { static RequestFactory parseAnnotations(Retrofit retrofit, Method method) { return new Builder(retrofit, method).build(); }... static final class Builder { Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; // GET annotations for network request methods: such as @get,@POST@HTTP this. MethodAnnotations = method.getannotations (); / / get online request method parameter type enclosing parameterTypes = method. The getGenericParameterTypes (); Annotation / / access network request parameters, such as @ Url, @ Path, @ Query such as this. ParameterAnnotationsArray = method. The getParameterAnnotations (); } //build method RequestFactory build() {// parse network request method Annotation Annotation: methodAnnotations) { parseMethodAnnotation(annotation); }... / / the type of analytical network request method parameters and annotations 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); }... return new RequestFactory(this); } // Parse the network request method annotation, see if the following looks familiar, Private void parseMethodAnnotation(Annotation Annotation) {if (Annotation instanceof DELETE) {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; }} // Parse network request method parameters with types and annotations private @nullable ParameterHandler<? > parseParameter( int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) { ParameterHandler<? > result = null; if (annotations ! = null) { for (Annotation annotation : annotations) { ParameterHandler<? > annotationAction = parseParameterAnnotation(p, parameterType, annotations, annotation); . result = annotationAction; }}... return result; } @nullable private ParameterHandler<? > parseParameterAnnotation( int p, Type type, Annotation[] annotations, Annotation Annotation Annotation if (Annotation instanceof Url) {... gotUrl = true; / / judgment parameter types if (type = = HttpUrl. Class | | type = = String. The class | | type = = URI. The class | | (type instanceof class && "android.net.Uri".equals(((Class<?>) type).getName()))) { return new ParameterHandler.RelativeUrl(method, p); }... } else if (annotation instanceof Path) { ... gotPath = true; Path path = (Path) annotation; String name = path.value(); validatePathName(p, name); Converter<? , String> converter = retrofit.stringConverter(type, annotations); return new ParameterHandler.Path<>(method, p, name, converter, path.encoded()); } else if (annotation instanceof Query) { ... } else if (annotation instanceof QueryName) { ... } else if (annotation instanceof QueryMap) { ... } else if (annotation instanceof Header) { ... } else if (annotation instanceof HeaderMap) { ... } else if (annotation instanceof Field) { ... } else if (annotation instanceof FieldMap) { ... } else if (annotation instanceof Part) { ... } else if (annotation instanceof PartMap) { ... } return null; // Not a Retrofit annotation. } } ... }Copy the code
  • ServiceMethod. ParseAnnotations finally is call HttpServiceMethod. ParseAnnotations ServiceMethod instance
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction; . Annotations [] annotations = method.getannotations (); Type adapterType; if (isKotlinSuspendFunction) { ... } else {/ / the return value type of online request method is requests the adapter type adapterType = method. The getGenericReturnType (); } // CallAdapter<ResponseT, ReturnT> CallAdapter = createCallAdapter(retrofit, method, adapterType, Annotations); Type responseType = callAdapter.responseType(); . ResponseType = responseVerter (retrofit, method, responseType); // Get the request Factory from retrofit. The default is OkHttpClient okHttp3.call.factory callFactory = retrofit.callFactory; if (! isKotlinSuspendFunction) { return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); }... } //Call Servicemethod static final class call Call <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; } // Implement the adapt method, Override protected ReturnT adapt(Call<ResponseT> Call, Object[] args) { return callAdapter.adapt(call); }}Copy the code
  • Here we know that loadServiceMethod(method).invoke(args) in Retrofit’s create method is actually the invoke method of the HttpServiceMethod called
  • So let’s look at the HttpServiceMethod invoke method, where the adapt method called is the adapt of callCaller, and the call passed in is of type OkHttpCall;
  @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }
Copy the code
  • You can see from the code above that the Call in Retrofit is actually an OkHttpCall

OkHttpCall’s synchronous request method execute

  • As we know from Retrofit’s create method above, call.execute in the code below actually calls the execute method of OkHttpCall
val apiService = retrofit.create(ApiService::class.java)
val call = apiService.searRepos(1, 5)
val response: Response<RepoList> = call.execute()
if (response.isSuccessful) {
    val repo = response.body()
    LjyLogUtil.d(repo.toString())
} else {
    LjyLogUtil.d(IOException("Unexpected code $response").message)
}
Copy the code
  • The okHttpCall. execute code, which creates a Call object from the okHttp library, shows that network requests in Retrofit are actually handled by okHttp;
@Override public Response<T> execute() throws IOException { okhttp3.Call call; synchronized (this) { if (executed) throw new IllegalStateException("Already executed."); executed = true; // create okhttp3.Call Call = getRawCall(); } if (canceled) { call.cancel(); } return parseResponse(call.execute()); } @GuardedBy("this") private okhttp3.Call getRawCall() throws IOException { okhttp3.Call call = rawCall; if (call ! = null) return call; . return rawCall = createRawCall(); . } 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
  • Create okHttp3. Call from createRawCall callFactory.newCall The newCall entry is a request object created with requestFactory.create

OkHttpCall’s asynchronous request method enqueue

  • The same is true for asynchronous requests made asynchronously through okhttp. Call
@Override public void enqueue(final Callback<T> callback) { Objects.requireNonNull(callback, "callback == null"); okhttp3.Call call; Throwable failure; synchronized (this) { if (executed) throw new IllegalStateException("Already executed."); executed = true; call = rawCall; failure = creationFailure; if (call == null && failure == null) { try { call = rawCall = createRawCall(); } catch (Throwable t) { throwIfFatal(t); failure = creationFailure = t; } } } if (failure ! = null) { callback.onFailure(this, failure); return; } if (canceled) { call.cancel(); } call.enqueue( new okhttp3.Callback() { @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) { Response<T> response; try { response = parseResponse(rawResponse); } catch (Throwable e) { throwIfFatal(e); callFailure(e); return; } try { callback.onResponse(OkHttpCall.this, response); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); // TODO this is not great } } @Override public void onFailure(okhttp3.Call call, IOException e) { callFailure(e); } private void callFailure(Throwable e) { try { callback.onFailure(OkHttpCall.this, e); } catch (Throwable t) { throwIfFatal(t); t.printStackTrace(); // TODO this is not great } } }); }Copy the code

The parseResponse method of OkHttpCall

  • OkHttpCall’s synchronous asynchronous request calls the parseResponse method, which looks like this:

The body by T = responseConverter. Convert (catchingBody), with data parser to parse the response body, ResponseConverter is the Converter returned by ServiceMethod’s build method calling createResponseConverter.

Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException { ResponseBody rawBody = rawResponse.body(); // Remove the body's source (the only stateful object) so we can pass the response along. rawResponse = rawResponse .newBuilder() .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) .build(); int code = rawResponse.code(); if (code < 200 || code >= 300) { try { // Buffer the entire body to avoid future I/O. ResponseBody bufferedBody = Utils.buffer(rawBody); return Response.error(bufferedBody, rawResponse); } finally { rawBody.close(); } } if (code == 204 || code == 205) { rawBody.close(); return Response.success(null, rawResponse); } ExceptionCatchingResponseBody catchingBody = new ExceptionCatchingResponseBody(rawBody); try { T body = responseConverter.convert(catchingBody); return Response.success(body, rawResponse); } catch (RuntimeException e) { // If the underlying source threw an exception, propagate that rather than indicating it was // a runtime exception. catchingBody.throwIfCaught(); throw e; }}Copy the code

Design patterns in Retrofit

Builder mode

  • The construction and representation of complex objects are separated to simplify the construction of complex objects
  • To prevent too many constructor parameters, resulting in user inconvenience, through the chain call different methods to set different parameters
val retrofit = Retrofit.Builder()
    .baseUrl(baseUrl)
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build()
Copy the code
  • To learn more about Builder mode, see Android Design Pattern-2 builder Mode

The factory pattern

  • Separating “operations that instantiate classes” from “operations that use objects” reduces coupling, makes it easier to scale, and benefits product consistency
Val Retrofit = retrofit.builder ().baseurl (baseUrl).client(okHttpClient) // The custom Converter must be placed before the official supplied Converter .addConverterFactory(StringConverterFactory.create()) .addConverterFactory(MapConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(LjyCallAdapterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build()Copy the code
  • To learn more about factory mode, see:
    • Android Design Mode -4.1- Simple Factory Mode
    • Android Design Mode -4.2- Factory Method mode
    • Android Design Pattern -5- Abstract Factory Pattern

The strategy pattern

– You can switch between policy classes. Because all policy classes implement the same interface, you can switch between them.

  • Easy to expand, add a new policy only need to add a specific policy class, basically do not need to change the original code, in line with the “open closed principle”
val retrofit = Retrofit.Builder()
    .baseUrl(baseUrl)
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addCallAdapterFactory(Java8CallAdapterFactory.create())
    .addCallAdapterFactory(GuavaCallAdapterFactory.create())
    .build()
Copy the code
  • To learn more about policy mode, see Android Design Mode -6- Policy Mode

Observer model

  • Define a one-to-many dependency between objects so that whenever an object changes state, all dependent objects are notified and automatically updated
Call. Enqueue (object: Callback<RepoList> {override fun onResponse(call: Call<RepoList>, result: Response<RepoList>) { if (result.body() ! = null) { val repoResult: RepoList = result.body()!! for (it in repoResult.items) { LjyLogUtil.d("${it.name}_${it.starCount}") } } } override fun onFailure(call: Call<RepoList>, t: Throwable) { LjyLogUtil.d("onFailure:${t.message}") } })Copy the code
  • To learn more about observer mode, see Android Design Mode -11- Observer Mode

Adapter mode

  • Defines a wrapper class for wrapping objects with incompatible interfaces
  • You can make unrelated classes run together, improving class reuse
final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  private final @Nullable Executor callbackExecutor;

  DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  @Override
  public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    ...
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

    final Executor executor =
        Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
            ? null
            : callbackExecutor;

    return new CallAdapter<Object, Call<?>>() {
      @Override
      public Type responseType() {
        return responseType;
      }

      @Override
      public Call<Object> adapt(Call<Object> call) {
        return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }
}
Copy the code
  • The most common use of Adapter mode in Android is listView, recycleView Adapter
  • To learn more about the adapter pattern, see Android Design Pattern-19 adapter Pattern

Decorative pattern

  • Dynamically extending the functionality of an implementation class, decorator classes and decorator classes can develop independently without coupling to each other
ExecutorCallbackCall is the decorator, ExecutorCallbackCall<T> implements Call<T> {final Executor implements Call<T> 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) { callbackExecutor.execute( () -> { if (delegate.isCanceled()) { // Emulate OkHttp's 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(() -> callback.onFailure(ExecutorCallbackCall.this, t)); }}); }}Copy the code
  • To learn more about decoration mode, see Android Design Mode -20- Decoration Mode

The appearance model

  • An interface that provides external access to complex modules or subsystems
  • Wrap one or more complex classes in a subsystem by creating a unified class
  • The Retrofit class is the facade class that the Retrofit framework provides to us
public final class Retrofit { private final Map<Method, ServiceMethod<? >> serviceMethodCache = new ConcurrentHashMap<>(); final okhttp3.Call.Factory callFactory; final HttpUrl baseUrl; final List<Converter.Factory> converterFactories; final List<CallAdapter.Factory> callAdapterFactories; final @Nullable Executor callbackExecutor; final boolean validateEagerly; . }Copy the code
  • To learn more about appearance mode, see Android Design Mode -22- Appearance Mode

The proxy pattern

  • Also known as the delegation mode, indirectly access the target object, divided into static proxy and dynamic proxy
//Retrofit.create public <T> T create(final Class<T> service) { validateServiceInterface(service); Return (T) // create an instance of the interface through the dynamic Proxy proxy.newProxyInstance (// parameter 1: classLoader service.getclassLoader (), // Parameter 2: array of interface types new Class<? >[] {service}, // Parameter 3: new InvocationHandler() {... }); }Copy the code
  1. Static agent
abstract class AbsObject { abstract fun doSomething() } class RealObject : AbsObject() { override fun doSomething() { LjyLogUtil.d("RealObject.doSomething") } } class ProxyObject(private val realObject: RealObject) : AbsObject() { override fun doSomething() { LjyLogUtil.d("before RealObject") realObject.doSomething() Ljylogutil.d ("after RealObject")}} val proxyObject = proxyObject (RealObject) proxyObject.doSomething()Copy the code
  1. A dynamic proxy
interface Subject { fun doSomething() } class Test : Subject { override fun doSomething() { LjyLogUtil.d("Test.doSomething") } } class DynamicProxy(private val target: Subject) : InvocationHandler { override fun invoke(proxy: Any? , method: Method? , args: Array<out Any>?) : Any? {LjyLogUtil. D (" Proxy: ${Proxy? .javaClass? .name}") ljylogutil. d("before target") //Kotlin invoke = method! .invoke(target, *(args ? : EmptyArray ()) ljylogutil. d(" After Target ") return invoke}}  val test = Test() val myProxy = DynamicProxy(test) val subject: Subject = Proxy.newProxyInstance( test.javaClass.classLoader, test.javaClass.interfaces, myProxy ) as Subject subject.doSomething() LjyLogUtil.d("subject.className:" + subject.javaClass.name)Copy the code
  • To learn more about proxy mode, see Android Design Mode 17 proxy Mode

Refer to the article

  • square/retrofit
  • Retrofit is Google’s official explanation
  • Do you really use Retrofit2? Retrofit2 complete tutorial
  • Android Web programming (XI) source code parsing Retrofit
  • Android: Take you through Retrofit 2.0 source code hand by hand
  • Source code analysis of Android mainstream tripartite library

My name is Jinyang. If you want to learn more about jinyang, please pay attention to the wechat public number “Jinyang said” to receive my latest articles