Retrofit源碼解讀並手擼一個Retrofit

學習目標:

  • 搞明白Retrofit實現原理
  • 手擼一個簡易版的Retrofit

如何使用Retrofit

一、添加依賴html

implementation 'com.squareup.retrofit2:retrofit:(insert latest version)'
複製代碼

備註:本文使用的是2.6.2版本java

二、如何使用android

2.一、建立接口文件:WanAndroidApigit

interface WanAndroidApi {
            @GET("banner/json")
            fun getBannerData(): Call<ResponseBody>
        }
複製代碼

2.二、構造Retrofit對象併發起一個簡單的請求github

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()}")
            }
        })
複製代碼

2.三、請求打印結果json

System.out: onResponse:   {"data":[{"desc":"Android高級進階直播課免費學習","id":23,"imagePath":"https://wanandroid.com/blogimgs/67c28e8c-2716-4b78-95d3-22cbde65d924.jpeg","isVisible":1,"order":0,"title":"Android高級進階直播課免費學習","type":0,"url":"https://url.163.com/4bj"},{"desc":"一塊兒來作個App吧","id":10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":0,"title":"一塊兒來作個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":"咱們新增了一個經常使用導航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 中文社區 ","type":1,"url":"https://flutter.cn/"}],"errorCode":0,"errorMsg":""}
複製代碼

單純的Retrofit用法就是這麼簡單,幾行代碼實現一個網絡請求,固然配合RxJava一塊兒使用是近幾年來最主流的網絡架構,本文是解讀Retrofit源碼就不對RxJava用法作過多說明了。設計模式

源碼分析

分析源碼以前須要作些什麼

閱讀任何源碼都要帶着疑問去閱讀,經過閱讀去尋找答案,就能更深入的理解源碼。api

筆者在使用的時候心中就有兩個疑問:緩存

  • 一、爲何定義一個interface就能調用接口方法進行請求
  • 二、Retrofit內部究竟是如何工做的

接下來就讓咱們帶着疑問去從源碼中尋找答案吧。bash

從哪裏開始分析源碼

一、咱們通常都是先建立一個接口類:WanAndroidApi

備註:這個接口裏邊是各類註解,關於註解的用法請移步什麼是註解

interface WanAndroidApi {
            @GET("banner/json")
            fun getBannerData(): Call<ResponseBody>
        }
複製代碼

二、而後經過retrofit.create(WanAndroidApi::class.java)返回一個WanAndroidApi對象,而後調用這個對象的getBannerData()方法進行網絡請求。

看到此處就會心有疑問,咱們知道接口是不能直接建立對象的,爲何經過retrofit.create()以後就能建立WanAndroidApi對象吶,接下來咱們看下create方法的源碼

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    //看到Proxy彷佛有點明白,是經過動態代理去處理的
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        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);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
                    //重點1                  //重點2
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }
複製代碼

retrofit核心就是使用動態代理建立接口代理對象進行處理的,關於動態代理的用法請移步Java 動態代理詳解10分鐘看懂動態代理設計模式

源碼解析---重點1---loadServiceMethod(method)

ServiceMethod<?> loadServiceMethod(Method method) {
    //首先從緩存中獲取ServiceMethod
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      //沒有緩存過的話再去建立並存入到緩存
      if (result == null) {
        //重點3
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }
複製代碼

經過緩存讀取,沒有緩存的話經過ServiceMethod.parseAnnotations建立出ServiceMethod對象,而後緩存起來,方便下次使用

源碼解析---重點3---ServiceMethod.parseAnnotations(this, method)

abstract class ServiceMethod<T> {
    static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    //重點4: 經過RequestFactory.parseAnnotations解析註解中請求方法和參數信息封裝到RequestFactory中
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = 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.");
    }
    //重點5:把RequestFactory傳給HttpServiceMethod進一步解析
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

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

複製代碼

經過RequestFactory.parseAnnotations(retrofit, method)解析註解中請求方法和參數信息封裝到RequestFactory中

源碼解析---重點5---HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory)

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
   
    Annotation[] annotations = method.getAnnotations();
    ······
    //Retrofit的CallAdapter
    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) {
      //重點6 
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    }
    ······
  }
複製代碼

源碼解析---重點6---CallAdapted

CallAdapted(RequestFactory requestFactory, okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, ReturnT> callAdapter) {
      //裏邊這是各類參數的賦值,看super方法便可,把上邊全部解析出來的參數賦值到對應的對象
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
    }
    
複製代碼

至此這條分析路線1->3->4->5->6邏輯所有跟蹤完畢,此時回到回到文中標記的重點1處,爲了方便查看直接在下邊貼出對應代碼

//重點1                  //重點2
    return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
複製代碼

經過以上分析咱們知道loadServiceMethod方法就是解析各類註解參數設置給相應對象,而後調用invoke方法,咱們再來看看invoke方法作了什麼

源碼解析---重點2---invoke

@Override final @Nullable ReturnT invoke(Object[] args) {
    //重點7
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }
複製代碼

invoke方法建立了OkHttpCall對象,咱們看看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));

    return call;
  }

}
複製代碼

OkHttpCall實現了Call接口方法,在裏邊是經過okhttp進行網絡請求,並把請求結果經過callback回調出去,至此整個流程已經所有分析完畢

經過上邊對源碼的分析咱們能夠總結retrofit的幾個關鍵點:

  • 經過註解方式把請求path、參數等信息標記在xxxApi接口中;
  • retrofit.create裏邊的動態代理方法返回xxxApi的代理對象;
  • 代理方法內部進行註解解析,並構建出okhttp對象方式請求,返回call對象;
  • 調用xxxApi的接口方法返回的call對象經過callback拿到請求結果;

接下來就咱們就仿照retrofit的實現方式手擼一個簡易版的retrofit試試

手擼一個Retrofit

參照Retrofit咱們須要定一個請求的註解方法 以get方法爲例

@kotlin.annotation.Target(AnnotationTarget.FUNCTION)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class GET(
    val value: String = ""
)
複製代碼

有了GET請求註解咱們就能夠定義咱們的接口api了,細心的你會發下接口裏邊須要一個返回值Call,那咱們就定義一個Call,代碼以下

//參照Rerofit的Call,咱們只寫一個方法enqueue便可
interface Call<T> {
    fun enqueue(callBack: CallBack<T>)
}
複製代碼

這裏又用到了CallBack,那繼續寫一個CallBack接口

interface CallBack<T> {
    //請求成功回調
    fun onResponse(response: Response?)
    //請求失敗回調
    fun onFailure(t: Throwable)
}
複製代碼

有了以上定義的方法咱們就能夠寫ApiService

interface MyApiService {
    @GET("banner/json")
    fun getBannerData():Call<ResponseBody>
}
複製代碼

定義好ApiService接口以後就要建立一個咱們本身的retrofit

建立MyRetrofit類

class MyRetrofit(private val baseUrl: String?) {
    //也使用Builder模式構建retrofit對象
    class Builder {
        var baseUrl: String? = null
        fun setBaseUrl(baseUrl: String): Builder {
            this.baseUrl = baseUrl
            return this
        }

        fun build(): MyRetrofit {
            return MyRetrofit(baseUrl)
        }
    }
    //參照retrofit的源碼使用Proxy.newProxyInstance動態代理處理
    fun <T> create(service: Class<T>): T {
        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
    }
}
複製代碼

在判斷是否是GET請求,是的話解析註解path和參數等信息,拼接一個完整的url,而後構建出okhttp對象進行網絡請求,把結果回調給callback 至此簡易版的retrofit已經寫完了,接下來咱們就驗證一下是否能請求成功

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}")
            }
        })
複製代碼

請求結果以下:

System.out: MyRetrofit---->onResponse:   {"data":[{"desc":"Android高級進階直播課免費學習","id":23,"imagePath":"https://wanandroid.com/blogimgs/67c28e8c-2716-4b78-95d3-22cbde65d924.jpeg","isVisible":1,"order":0,"title":"Android高級進階直播課免費學習","type":0,"url":"https://url.163.com/4bj"},{"desc":"一塊兒來作個App吧","id":10,"imagePath":"https://www.wanandroid.com/blogimgs/50c115c2-cf6c-4802-aa7b-a4334de444cd.png","isVisible":1,"order":0,"title":"一塊兒來作個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":"咱們新增了一個經常使用導航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 中文社區 ","type":1,"url":"https://flutter.cn/"}],"errorCode":0,"errorMsg":""}
複製代碼

結果符合咱們的預期,證實咱們前面的分析是正確的!

最後

能夠看看咱們只建立了幾個類寫了幾行代碼就能夠像使用Retrofit同樣的用法進行網絡請求了,固然示例代碼只是實現了一個最基礎的,畢竟源碼仍是很複雜的,他們考慮了擴展性,易用性等,裏邊各類封裝各類設計模式隨處可見,是一個很是值得學習的庫,感謝Retrofit團隊的付出。無論他封裝的再複雜,萬變不離其宗,咱們掌握了它的實現原理之後再項目中遇到問題就能作到心中有數,作到知己知彼,方能解出bug。

相關文章
相關標籤/搜索