Retrofit流水賬

woodyxt發表於2018-04-27

出來混遲早要還,先記個賬吧

馬瘦毛長蹄子肥,兒子偷爹不算賊,瞎大爺娶個瞎大奶奶,老兩口過了多半輩兒誰也沒看見誰!

準備

 val INSTANCE: Retrofit = Retrofit.Builder()
                .baseUrl("http://www.wanandroid.com/")
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .client(httpClient)
                .build()
複製程式碼

build()一個Retrofit物件

open interface HomeList {
    @GET("article/list/{pag}/json")
    fun getHomeList(@Path("pag") pag:Int): Observable<HomeBean>
}
複製程式碼

準備一個介面

呼叫

var homeList: HomeList? = retrofit.create(HomeList::class.java)
val subscribe = homeList!!.getHomeList(HomeListState.page)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe( success, failed)
複製程式碼

建立代理

為什麼介面可以建立物件?為什麼介面的方法可以呼叫? 動態代理登場

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public 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);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }
複製程式碼

撇開驗證,看看代理幹嘛了

Proxy 提供用於建立動態代理類和例項的靜態方法,它還是由這些方法建立的所有動態代理類的超類。

建立某一介面 Foo 的代理:

  InvocationHandler handler = new MyInvocationHandler(...);
      Class proxyClass = Proxy.getProxyClass(
      Foo.class.getClassLoader(), new Class[] { Foo.class });
      Foo f = (Foo) proxyClass.
          getConstructor(new Class[] { InvocationHandler.class }).
          newInstance(new Object[] { handler });
複製程式碼

或使用以下更簡單的方法:

  Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                          new Class[] { Foo.class },
                                          handler);
複製程式碼

動態代理類(dynamic proxy class)(以下簡稱為代理類(proxy))是一個在執行時動態implements多個介面的類(也就是可以動態建立同時實現了多個介面的物件,同時這個物件還是Proxy類的子類),該類具有下面描述的行為。

代理介面代理類實現的一個介面。 代理例項 是代理類的一個例項。每個代理例項都可以通過實現介面InvocationHandler獲得一個關聯的呼叫處理程式(invocation handler) 物件。通過其中某個代理介面的代理例項上的方法呼叫將被指派到例項的InvocationHandler.invoke方法,並傳遞代理例項、識別呼叫方法的 java.lang.reflect.Method 物件以及包含引數的 Object 型別的陣列。呼叫處理程式以適當的方式處理編碼的方法呼叫,並且它返回的結果將作為代理例項上方法呼叫的結果返回。

代理類具用以下屬性:

  • 如果所有代理介面都是public的那麼所建立出來的代理類就都是publicfinal的,而且不是abstract的如果有任何一個代理介面不是public的那麼所建立出來的代理類就都不是public
    • 原文有兩條可以直接翻譯成程式碼,但是簡直不是人話
    • Proxy classes are public, final, and not abstract if all proxy interfaces are public.
    • Proxy classes are non-public, final, and not abstract if any of the proxy interfaces is non-public.
  • 代理類在建立前名稱沒有限制。但是,以字串 "$Proxy" 開頭的類名空間應該為代理類保留。
  • 代理類繼承自 java.lang.reflect.Proxy
  • 代理類會按建立時指定的介面的順序準確地實現每個介面。
  • 如果代理類實現了非public介面,那麼它將會被定義在與該介面相同的包中。否則,代理類的包也是未指定的。注意,包密封將不阻止代理類在執行時在特定包中的成功定義,也不會阻止相同類載入器和帶有特定簽名的包所定義的類。
  • 由於代理類將實現所有在其建立時指定的介面,所以對其 Class 物件呼叫 getInterfaces 將返回一個包含相同介面列表的陣列(按其建立時指定的順序),對其 Class 物件呼叫 getMethods 將返回一個包括這些介面中所有方法的 Method 物件的陣列,並且呼叫 getMethod 將會在代理介面中找到期望的一些方法。
  • 如果給 Proxy.isProxyClass 方法傳遞的代理Class是由 Proxy.getProxyClass 返回的Class,或是由 Proxy.newProxyInstance 返回的物件的Class,則該方法返回 true,否則返回 false
  • 代理類的 java.security.ProtectionDomain 與由引導類載入器載入的系統類相同,好比 java.lang.Object,原因是代理類的程式碼由受信任的系統程式碼生成。此保護域通常被授予 java.security.AllPermission
  • 每個代理類都有一個公共構造方法,它接收一個引數實現自介面 InvocationHandler此方法用於設定代理例項的呼叫處理程式,但是並非必須使用反射 API 才能訪問公共構造方法,通過呼叫 Proxy.newInstance 方法其中利用了 Proxy.getProxyClass 來呼叫處理程式後呼叫構造方法,也可以建立代理例項。

代理例項具有以下屬性:

  • 給定一個代理例項proxy和一個由其代理類Foo實現的介面之一,下面的表示式將返回true:
    proxy instanceof Foo
    複製程式碼
    並且以下的強制轉換操作將會成功(而不丟擲 ClassCastException):
    (Foo) proxy
    複製程式碼
  • 每個代理例項都有一個關聯的呼叫處理程式,即傳遞給它的建構函式的呼叫處理程式(invocation handler)。靜態方法Proxy.getInvocationHandler將返回與代理例項關聯的呼叫處理程式作為其引數。
  • 代理例項上的介面方法呼叫將被編碼,並按照InvocationHandler#invoke方法的文件中所描述的那樣,傳送到呼叫處理程式(invocation handler)的InvocationHandler.invoke方法中。
  • 在代理物件中呼叫hashCodeequalstoString這種在java.lang.Object中宣告的方法同樣會按照上述規則編碼併傳送。被宣告為Method型別的物件會視作java.lang.Object物件傳送給invoke方法。繼承自Object的代理例項的其他公共方法不會被代理類覆蓋,因此這些方法的呼叫行為就像它們對java.lang.Object的例項一樣。

這貼怎麼變成翻譯貼了(╯°Д°)╯︵ ┻━┻總之剩下的註釋就是說當有多個同名方法的情況下的處理規則實在懶得翻了——斜體字掀桌子更有氣勢了(  ̄ 3 ̄)y▂ξ

所以Retrofit .create方法其本質在執行時建立一個物件,而這個物件呼叫介面宣告的方法時,會派發給InvocationHandler介面的實現類並呼叫invoke方法,而引數中的Method method也就是當前呼叫方法的Method物件,其中會包括方法在宣告時所寫的很多資訊,比如各種註解,不管是方法上的還是引數上的,而用到這些資訊的地方:

ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
複製程式碼

就是這句建立了一個ServiceMethod物件,這個類的程式碼太長了他的主要作用就是根據方法和引數上的註解生成request和response:

 /** Builds an HTTP request from method arguments. */
  Request toRequest(@Nullable Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);

    @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
    ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

    int argumentCount = args != null ? args.length : 0;
    if (argumentCount != handlers.length) {
      throw new IllegalArgumentException("Argument count (" + argumentCount
          + ") doesn't match expected count (" + handlers.length + ")");
    }

    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }

    return requestBuilder.build();
  }

  /** Builds a method return value from an HTTP response body. */
  R toResponse(ResponseBody body) throws IOException {
    return responseConverter.convert(body);
  }
複製程式碼

生成回撥器

那麼這兩個方法是在哪裡會呼叫呢OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);這句程式碼中將serviceMethod物件設定給了OkHttpCall這個類可以說是OkHttp庫中Call類的包裝類由於都是自家產物兩者抽象層的方法末班都是一樣的:

Response<T> execute()
void enqueue(Callback responseCallback)
boolean isExecuted()
void cancel()
boolean isCanceled()
Call clone()
Request request()
複製程式碼

這些方法中enqueueexecute這些方法都會發起請求這時就會呼叫createRawCall方法:

  private okhttp3.Call createRawCall() throws IOException {
    Request request = serviceMethod.toRequest(args);
    okhttp3.Call call = serviceMethod.callFactory.newCall(request);
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }
複製程式碼

可以看到此處先呼叫了serviceMethod.toRequest方法生成request物件並利用此物件生成OkHttp庫中的Call物件然後在requestenqueueexecute方法中各種呼叫OkHttp庫的對應的方法,也就是到這一步真正發起了請求。

請求

但是現在這個OkHttpCall物件只是被建立出來其中的邏輯尚未被執行,它被放到了serviceMethod.callAdapter.adapt(okHttpCall);中,而跟蹤程式碼可知callAdapt物件是根據呼叫方法的返回值型別從最初構建Retrofit物件時.addCallAdapterFactory(RxJava2CallAdapterFactory.create())設定進去的介面卡列表中取出的,這也是為什麼他可以適配多種回撥方式的原因,此時我們才得到了真正的Call根據不同介面卡的呼叫方法執行程式碼之後實際上會呼叫到okhttp3.Call這個類中的enqueue或者execute方法此時真正發起請求。

響應

而當請求響應時在OkHttpCall類中會先使用Response<T> parseResponse(okhttp3.Response rawResponse)方法將響應包裝成自己的Response在這個過程中如果響應code就會呼叫serviceMethod.toResponse方法跟蹤程式碼這個方法中也就是利用構建Retrofit物件時.addConverterFactory(GsonConverterFactory.create())設定進去的變換器來解析Response並將結果返回,然後傳送給回撥介面。

在鄰居穿牆而過的歌聲中算完了帳,突然覺得沒有充QQ音樂的會員真是賺了,本文從頭流到尾,應該不適合飯後觀看,看久了會暈,暈了會想吐,吐了就會浪費糧食,所以我把提示寫在了最後,不要客氣,我是紅領巾。

相關文章