Learning Objectives:

  • Understand how Retrofit works
  • Masturbating a simple version of Retrofit

How to useRetrofit

1. Add dependencies

implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'
Copy the code

Note: Version 2.6.2 was used in this article

2. How to use it

2.1. Create interface file: WanAndroidApi

        interface WanAndroidApi {
            @GET("banner/json")
            fun getBannerData(): Call<ResponseBody>
        }
Copy the code

2.2. Construct a Retrofit object and make a simple request

        val baseUrl = "https://www.wanandroid.com/"

        val retrofit = Retrofit.Builder().baseUrl(baseUrl).build()

        val wanAndroidApi = retrofit.create(WanAndroidApi::class.java)

        wanAndroidApi.getBannerData().enqueue(object : Callback<ResponseBody> {
            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                println("onFailure:   ${t.message}")

            }
            override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
                println("onResponse:   ${response.body()? .string()}")}})Copy the code

2.3. Request to print the result

        System.out: onResponse:   {"data": [{"desc":"Android Advanced live streaming course for free"."id": 23."imagePath":"https://wanandroid.com/blogimgs/67c28e8c-2716-4b78-95d3-22cbde65d924.jpeg"."isVisible": 1,"order": 0."title":"Android Advanced live streaming course for free"."type": 0."url":"https://url.163.com/4bj"}, {"desc":"Let's make an App."."id": 10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png"."isVisible": 1,"order": 0."title":"Let's make an App."."type": 1,"url":"https://www.wanandroid.com/blog/show/2"}, {"desc":""."id": 6,"imagePath":"https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png"."isVisible": 1,"order": 1,"title":"We have added a common navigation Tab~"."type": 1,"url":"https://www.wanandroid.com/navi"}, {"desc":""."id": 20."imagePath":"https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png"."isVisible": 1,"order": 2."title":"Flutter Chinese Community"."type": 1,"url":"https://flutter.cn/"}]."errorCode": 0."errorMsg":""}
Copy the code

Simple Retrofit usage is as simple as that, a few lines of code implementing a network request, and of course RxJava is the most popular network architecture in recent years. This article will read Retrofit source code without saying too much about RxJava usage.

Source code analysis

What do I need to do before analyzing the source code

Read any source code with questions to read, through reading to find the answer, you can understand the source code more deeply.

The author has two questions in mind when using:

  • 1. Why is it possible to call interface methods when you define an interface
  • 2. How does Retrofit actually work inside

Next let us take questions from the source code to find the answer.

Where to start analyzing the source code

1. We usually start with an interface class: WanAndroidApi

Note: this interface contains various annotations, please go to the usage of annotations. What are annotations

        interface WanAndroidApi {
            @GET("banner/json")
            fun getBannerData(): Call<ResponseBody>
        }
Copy the code

2, and then through the retrofit. The create (WanAndroidApi: : class. Java) returns a WanAndroidApi object, and then call the object’s getBannerData () method for network requests.

We know that interfaces cannot create objects directly, so why passretrofit.create()And then you can create itWanAndroidApiObject, let’s look at the source code for the create method

  public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if(validateEagerly) { eagerlyValidateMethods(service); } // Proxy is a dynamic Proxyreturn(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)) {
              returnplatform.invokeDefaultMethod(method, service, proxy, args); } // Key 1 // key 2return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }
Copy the code

At the heart of Retrofit is the use of dynamic proxies to create interface proxy objects for processing. For details on dynamic proxies, see Java Dynamic Proxies and dynamic Proxy Design Patterns in 10 minutes

Source code analysis — emphasis 1–loadServiceMethod(method)

ServiceMethod<? > loadServiceMethod(Method Method) {// Get ServiceMethod from the cache first. > result = serviceMethodCache.get(method);if(result ! = null)returnresult; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); // Create it and store it in cache if it has not been cachedif(result = = null) {/ / key 3 result = ServiceMethod parseAnnotations (this, method); serviceMethodCache.put(method, result); }}return result;
  }
Copy the code

The cache read, no cache by ServiceMethod parseAnnotations create ServiceMethod object, and then cached, convenient to use next time

Source code analysis — emphasis 3–ServiceMethod.parseAnnotations(this, method)

abstract class ServiceMethod<T> { static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, {// Key 4: Through RequestFactory. ParseAnnotations parse request method and parameters in annotation information encapsulation into RequestFactory RequestFactory RequestFactory = RequestFactory.parseAnnotations(retrofit, method); TypereturnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(method,
          "Method return type must not include a type variable or wildcard: %s".returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void."); } // Key 5: Pass the RequestFactory to HttpServiceMethod for further parsingreturn HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

Copy the code

Through RequestFactory. ParseAnnotations (retrofit, method) analysis of the request method and parameter information encapsulation into RequestFactory

Source code analysis — key 5–HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory)

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations( Retrofit retrofit, Method method, RequestFactory requestFactory) { Annotation[] annotations = method.getAnnotations(); CallAdapter<ResponseT, ReturnT> CallAdapter = createCallAdapter(Retrofit, method, adapterType, annotations); ····· Retrofit ConverterAdapter Converter<ResponseBody, ResponseT> responseConverter = createResponseConverter(retrofit, method, responseType); okhttp3.Call.Factory callFactory = retrofit.callFactory;if(! IsKotlinSuspendFunction) {// key 6returnnew CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter); }......}Copy the code

Source code analysis — key point 6–CallAdapted

CallAdapted(RequestFactory requestFactory, okhttp3.Call.Factory callFactory, Converter<ResponseBody, ResponseT> responseConverter, CallAdapter<ResponseT, ReturnT> CallAdapter) {ResponseT <ResponseT, ReturnT> CallAdapter) Assign all parsed parameters to the corresponding object super(requestFactory, callFactory, responseConverter); this.callAdapter = callAdapter; }Copy the code

At this point, all the logic of this analysis route 1->3->4->5->6 has been tracked. At this time, it is back to the point 1 marked in the article, and the corresponding code is directly posted below for convenience

// Key 1 // key 2returnloadServiceMethod(method).invoke(args ! = null ? args : emptyArgs);Copy the code

The loadServiceMethod method parses the various annotation parameter Settings to the corresponding object, and then calls the invoke method

Source code analysis — emphasis 2–invoke

@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 an OkHttpCall object. What is an OkHttpCall

final class OkHttpCall<T> implements Call<T> {

  @Override public synchronized Request request() {
    okhttp3.Call call = rawCall;
    try {
      return (rawCall = createRawCall()).request();
    }
  }

  @Override public void enqueue(final Callback<T> callback) {
    ...
    call = rawCall = createRawCall();
    call.enqueue(new okhttp3.Callback() { @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) { Response<T> response; try { response = parseResponse(rawResponse); } callback.onResponse(OkHttpCall.this, response); } @Override public void onFailure(okhttp3.Call call, IOException e) { callFailure(e); } private void callFailure(Throwable e) { callback.onFailure(OkHttpCall.this, e); }}); } @Override public Response<T> execute() throws IOException { okhttp3.Call call; . call = rawCall = createRawCall();return parseResponse(call.execute());
  }

  private okhttp3.Call createRawCall() throws IOException {
    okhttp3.Call call = callFactory.newCall(requestFactory.create(args));

    returncall; }}Copy the code

OkHttpCall implements the Call interface method, in which the network request is made through OKHTTP, and the result of the request is called back through the callback, so far the whole process has been analyzed

From the source code analysis above, we can summarize retrofit’s key points:

  • Annotate the request path, parameters and other information mark in xxxApi interface;
  • The dynamic proxy method in retrofit.create returns the xxxApi proxy object;
  • The proxy method internally interprets annotations and constructs an OKHTTP object request that returns a Call object.
  • The call object returned by calling xxxApi’s interface method gets the result of the request through callback;

So what we’re going to do is we’re going to try a simple version of Retrofit

Hand lift a Retrofit

With Retrofit we need to specify a request annotation method for example the GET method

@kotlin.annotation.Target(AnnotationTarget.FUNCTION)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class GET(
    val value: String = ""
)
Copy the code

With the GET request annotation we can define our interface API. Careful you will notice that the interface needs a return value Call, so we will define a Call, as follows

Interface Call<T> {fun enqueue(callBack: callBack <T>)}Copy the code

CallBack is used here again, so continue to write a CallBack interface

Interface CallBack<T> {// Request success CallBack fun onResponse(response: response?) Fun onFailure(t: Throwable)}Copy the code

With the methods defined above we can write ApiService

interface MyApiService {
    @GET("banner/json")
    fun getBannerData():Call<ResponseBody>
}
Copy the code

Once the ApiService interface was defined, it was time to create our own RetroFIT

Create MyRetrofit class

class MyRetrofit(private val baseUrl: String?) Class Builder {var baseUrl: String? = null funsetBaseUrl(baseUrl: String): Builder {
            this.baseUrl = baseUrl
            return this
        }

        fun build(): MyRetrofit {
            returnFun <T> create(service: Class<T>): T {MyRetrofit(baseUrl)}}return Proxy.newProxyInstance(
            service.classLoader,
            arrayOf(service),

            InvocationHandler { any, method, arrayOfAnys ->
                var call: Call<T>? = null
                val annotations = method.declaredAnnotations
                for (annotation in annotations) {
                    call = parseHttpMethodAndPath(annotation)
                }

                return@InvocationHandler call

            }
        ) as T
    }

    private fun <T> parseHttpMethodAndPath(annotation: Annotation): Call<T>? {
        var call: Call<T>? = null
        if (annotation is GET) {
            val path = annotation.value
            val url = "$baseUrl$path"

            println("url= $url")

            val okHttpClient: OkHttpClient = OkHttpClient().newBuilder().build()
            val request: Request = Request.Builder().url(url).build()
            call = object : Call<T> {
                override fun enqueue(callBack: CallBack<T>) {
                    okHttpClient.newCall(request).enqueue(object : Callback {
                        override fun onFailure(call: okhttp3.Call, e: IOException) {
                            callBack.onFailure(e)
                        }

                        override fun onResponse(call: okhttp3.Call, response: Response) {
                            callBack.onResponse(response)
                        }
                    })
                }
            }
        }
        return call
    }
}
Copy the code

After determining whether or not a GET request is made, parsing annotations such as path and parameters, stitching together a complete URL, then constructing an OKHTTP object to make a network request, and calling the result back to callback. Now that retrofit has been written, we can verify that the request succeeds

        val myRetrofit = MyRetrofit.Builder().setBaseUrl(baseUrl).build()
        val apiService = myRetrofit.create(MyApiService::class.java)

        apiService.getBannerData().enqueue(object : CallBack<ResponseBody> {
            override fun onResponse(response: okhttp3.Response?) {
                println("MyRetrofit---->onResponse:   ${response? .body()? .string()}")
            }
            override fun onFailure(t: Throwable) {
                println("MyRetrofit---->onFailure:   ${t.message}")}})Copy the code

The request result is as follows:

System.out: MyRetrofit---->onResponse:   {"data": [{"desc":"Android Advanced live streaming course for free"."id": 23."imagePath":"https://wanandroid.com/blogimgs/67c28e8c-2716-4b78-95d3-22cbde65d924.jpeg"."isVisible": 1,"order": 0."title":"Android Advanced live streaming course for free"."type": 0."url":"https://url.163.com/4bj"}, {"desc":"Let's make an App."."id": 10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png"."isVisible": 1,"order": 0."title":"Let's make an App."."type": 1,"url":"https://www.wanandroid.com/blog/show/2"}, {"desc":""."id": 6,"imagePath":"https://www.wanandroid.com/blogimgs/62c1bd68-b5f3-4a3c-a649-7ca8c7dfabe6.png"."isVisible": 1,"order": 1,"title":"We have added a common navigation Tab~"."type": 1,"url":"https://www.wanandroid.com/navi"}, {"desc":""."id": 20."imagePath":"https://www.wanandroid.com/blogimgs/90c6cc12-742e-4c9f-b318-b912f163b8d0.png"."isVisible": 1,"order": 2."title":"Flutter Chinese Community"."type": 1,"url":"https://flutter.cn/"}]."errorCode": 0."errorMsg":""}
Copy the code

The results met our expectations and proved our previous analysis correct!

The last

Can see we just created several class to write a few lines of code can be like using Retrofit the usage of the network request, of course, the sample code just realized one of the most basic, the source code is very complex, after all, they consider the scalability, ease of use, etc., in a variety of packaging a variety of design patterns can be seen everywhere, is a very worth learning library, Thanks to the Retrofit team. No matter how complex it is, all changes are inseparable from it. After we master its implementation principle, we can have a clear idea of the problems encountered in the project, and only by knowing ourselves and our enemies can we solve the bugs.