閱讀125 返回首頁    go 阿裏雲 go 技術社區[雲棲]


談談網絡庫和Retrofit

本文目錄如下

  1. 網絡模塊需要具備什麼能力
  2. 為什麼Retrofit是個好選擇
  3. Retrofit業務分析
  4. Retrofit技術點
  5. 設計模式

一.網絡模塊需要具備什麼能力?

常見的需求下圖:
screenshot.png

下麵解釋一下重要的部分.

1.支持緩存

為了讓頁麵快速展現,很多頁麵需要先加載緩存.
通用策略是

1.先加載緩存;
2.然後做網絡請求
3.網絡請求成功後刷新頁麵,並且更新緩存數據

這裏涉及到,緩存要存在哪裏. 一般也是兩種方案:

  1. 業務側做緩存. 每個頁麵自己維護緩存,一般存在db/sp/文件係統中
  2. 網絡側做緩存, 業務做網絡請求的時候,可以要求返回緩存數據.

方案1:適用於存儲需要檢索數據的情況. 比如數據存在db中,可以用sql查詢. 缺點是業務側需要實現緩存策略,較麻煩.
方案2: 業務側調用簡單,適用場景非常廣泛.

所以在網路側支持緩存, 是一個很普遍的方案.

2.AccessToken失效後重發失敗請求

這個是基於這幾種前提.

App有登錄功能
App登錄通過 AccessToken/RefreshToken實現

簡單說明 AccessToken失效後會發生什麼:

  1. AccessToken失效
  2. 做網絡請求,服務端會返回結果說AccessToken失效

  3. 客戶端利用RefreshToken 重新換AccessToken.
  4. 拿著新AccessToken .
  5. 業務側拿到正確結果

一般AccessToken失效是比較頻繁的, 如果沒有自動重新請求機製, 每次AccessToken失效用戶都能感知到,這樣體驗很不好. 所以一般要在網絡庫/登錄模塊之間或者上層做一個封裝解決這個問題..

3. 多級別取消網絡請求

最好是能:

  1. 取消一次網絡請求
  2. 某個頁麵退出時,能取消整個頁麵對應的網絡請求.

資源嗎,能省點就省點

4. 支持"同步/異步"請求

5. 支持選中在"異步線程/UI線程"回調

很多請求是需要需要刷UI的, 這樣就不需要主動post到UI線程了.

二.為什麼Retrofit是個好選擇

Retrofit-Github

選開源庫,有幾個點很重要.

1.開源庫要穩定,優先選擇有人維護,用戶量大的庫

成熟的項目明顯的坑少. 所以一個應用廣泛的庫是一個穩妥的選擇.

Retrofit: Square出品, 2萬+ star,一直在更新

2.開源庫的代碼結構不能太龐大,太複雜.

是代碼總會有bug的, 有問題要能看明白,可以選擇規避或者自行解決. 代碼龐大或者邏輯複雜時,排查問題就是一個很痛苦的過程

Retrofit 代碼量少,結構清晰

screenshot.png

3.拓展性好

業務是變化的, 要在預期的業務變化內, 當前庫要能cover住, 如果開發幾版發現不能滿足拓展的需求, 臨時換庫,那會帶來很多工作量和bug

Retrofit 在解耦上做的很棒,拓展性高

4.開源庫自身的依賴庫不能太多

在這點上很重要,引入一個庫如果要一並帶入很多它的依賴庫,會有很大的問題:
1. 會增大包的體積
2. 會讓庫變得不好維護,鏈條上的每個庫都可能有問題.
3. 最大的問題是, 這些依賴庫可能會和App的其他依賴庫衝突. 會產生類衝突,版本衝突. 排查這些衝突是一個很痛苦的過程.(在這點上, 集團的依賴庫這個問題尤為嚴重)

Retrofit目前依賴於Okhttp3, 考慮到OKhttp3也很流行,可以一並引入

三. Retrofit業務分析

做為一個網絡庫Okhttp的封裝, Retrofit需要處理3部分問題:

  1. 允許業務拓展哪些環節.
  2. 這些拓展的注冊,和什麼情況下生效
  3. 拓展是否靈活

1.拓展性

Retrofit允許配置的是 CallAdapter,RequestBodyConverter,ResponseConvert.

CallAdapter: 負責調用OkHttpCall發起請求,處理回調分發
RequestBodyConverter: 在構建Request的時候, 構建body
ResponseConverter: 轉換網絡請求結果.

Retrofit發起網絡請求最終走的的OKHttpCall.
網絡請求的流程套路大家都一樣, 都是:
"業務發起請求"->"構建Request"->"發起請求"->"解釋請求"->"回調業務".
下麵是他的調用流程, 藍色部分是自定義模塊.

screenshot.png

2. 功能的注冊和獲取

注冊

在Retrofit初始化的時候, 允許添加CallAdapterFactory和ConverterFactory.

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://httpbin.org")
    .addCallAdapterFactory(new ErrorHandlingCallAdapterFactory())
    .addConverterFactory(GsonConverterFactory.create())
    .build();

獲取

注冊和獲取的流程如下.
按注冊的順序獲取,隻要找到了就就結束查找.

screenshot.png

3.靈活的處理能力

每一個請求聲明是一個函數, 他包含3部分信息:

  1. Return Type
  2. Method Annotation
  3. Parameter Annotation

screenshot.png

對於CallAdapter/RequestBodyConverter/ResponseConvert可以獲取到這3部分信息.然後做相應操作,過程是非常靈活.

//CallAdapter.Fatory 可以獲取到 
//ReturnType,Method Annotation  
public abstract @Nullable CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit);

//Response Convert可以獲取到 
//Type,Method Annotation
Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit);

//Request Convert可以獲取到 
//Type,Method Annotation,Parameter Annotation  
Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) ;

四.Retrofit技術點

1.大量泛型/反射的操作

關於Type下麵這個文章講的比較好
Retrofit完全解析(三):Type<基礎詳解>

對於方法

@GET("/repos/{owner}/{repo}/contributors")
Call<List<Contributor>> contributors(
            @Path("owner") String owner,
            @Path("repo") String repo);

方法有三部分信息

  1. 函數返回值
  2. 方法體上的Annotaion
  3. 參數中的Annotation

Retrofit中根據這3部分信息, 匹配CallAdapter/Converter,生成ServiceMethod, 中間用了大量的泛型解釋.代碼集中體現在 Utils/ServiceMethod/CallAdapter/Converter上.

用到類型示例:  
-Type
-ParameterizedType 
-TypeVariable  
-GenericArrayType    
-WildcardType    
-Class.isAssignableFrom
-Class.isInterface()
-Class<?>[] getInterfaces()   
-Type[] Method.getGenericParameterTypes() 
-Annotation[][] Method.getParameterAnnotations()
-Method.getGenericReturnType()

2.類和參數保護

1.不能修改的類用final修飾
2.new ConcurrentHashMap<>()處理並發,取得時候加上synchronized判斷
3.adapterFactories = unmodifiableList(adapterFactories); // Defensive copy at call site. 做參數保護

3.類中自定義static實例化方法

public final class GsonConverterFactory extends Converter.Factory {

 public static GsonConverterFactory create() {
    return create(new Gson());
  }
}

4.網絡模擬服務MockWebServer

MockWebServer-github
com.squareup.okhttp3:mockwebserver
比較強大的網絡模擬工具, 配合Okhttp用很方便

MockWebServer server = new MockWebServer();
server.start();
server.enqueue(new MockResponse().setBody("{\"name\": \"Jason\"}"));
server.enqueue(new MockResponse().setBody("<user name=\"Eximel\"/>"));

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(server.url("/"))
    .addConverterFactory(new QualifiedTypeConverterFactory(
        GsonConverterFactory.create(),
        SimpleXmlConverterFactory.create()))
    .build();
Service service = retrofit.create(Service.class);

User user1 = service.exampleJson().execute().body();

5.對Object進行Array操作

Object values;
(T) Array.get(values, i);   
Array.getLength(values);  

6.利用了方法上的各種信息

screenshot.png

7.Retrofit業務流程圖

轉自:Retrofit分析-漂亮的解耦套路

五.設計模式

摘在:Retrofit分析-經典設計模式案例,
作者: stay4it

1.外觀模式

Retrofit給我們暴露的方法和類不多。核心類就是Retrofit,我們隻管配置Retrofit,然後做請求。剩下的事情就跟上層無關了,隻需要等待回調。這樣大大降低了係統的耦合度。對於這種寫法,我們叫外觀模式(門麵模式)。

幾乎所有優秀的開源library都有一個門麵。比如Glide.with(), ImageLoader.load(), Alamofire.request()。有個門麵方便記憶,學習成本低,利於推廣品牌。 Retrofit的門麵就是retrofit.create()

當我們自己寫的代碼的時候盡量也要這樣來做。

比如我們有一個獨立並公用的模塊,需要供其他模塊來調用。比如download,location,socialshare等

最好我們寫一個module,將所有相關的代碼都放在這個module中。這是第一步。

第二步,為你的module提供一個漂亮的門麵。比如下載的DownloadManager, 經緯度的LocationTracker, 社交分享的SocialManager。它們做為功能模塊的入口,要盡量的簡潔,方法命名好記易理解,類上要有完整的示例注釋

第三步,閉門造車。不管你在裏麵幹什麼,外麵都是不知道的,就像薛定諤的那隻貓,外層不調用它,永遠不知道它是否好用。
不過為了以後好維護,不給他人留坑,還是盡量寫的工整一些。

2.建造者模式

 Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(server.url("/"))
    .addConverterFactory(new StringConverterFactory())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .build();

3.動態代理

Java靜態代理和動態代理介紹

再來說動態代理。以往的動態代理和靜態代理使用的場景是類似的。都想在delegate調用方法前後做一些操作。如果我的代理類有很多方法,那我得額外寫很多代碼,所以這時候就引入了動態代理。通過動態設置delegate,可以處理不同代理的不同方法。看不懂沒關係,直接上代碼:

Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
     new InvocationHandler() {
          ....
          //為方法生成ServiceMethod
          ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            //ServiceMethod返回真正可以進行請求的CallAdapter
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

4.抽象工廠模式

內部Converter,CallAdapter都用了抽象工廠模式, InterfaceFactory放到了一個類中,減少類文件的數量, Factory生成對應Interface的類,明確匹配關係.

 public interface Converter<F, T> {
  T convert(F value) throws IOException;

  abstract class Factory {

    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(Type type,
        Annotation[] annotations, Retrofit retrofit) {
      return null;
    } 
}

5.適配器模式

如果你已經看過retrofit源碼,很可能被CallAdapter玩壞。這個CallAdapter不是那麼好理解。先拋開代碼,我們來看看適配器模式。

Adapter簡單來說,就是將一個已存在的東西轉換成適合我們使用的東西。就比方說電源Adapter。出國旅遊都要帶轉接頭。比方說,RecyclerView裏的Adapter是這麼定義的。 Adapters provide a binding from an app-specific data set to views

再回來看看Retrofit,為什麼我們需要轉接頭呢。那個被轉換的是誰?我們看看CallAdapter的定義。Adapts a {@link Call} into the type of {@code T}. 這個CallOkHttpCall,它不能被我們直接使用嗎?被轉換後要去實現什麼特殊的功能嗎?

我們假設下。一開始,retrofit隻打算在android上使用,那就通過靜態代理ExecutorCallbackCall來切換線程。但是後來發現rxjava挺好用啊,這樣就不需要Handler來切換線程了嘛。想要實現,那得轉換一下。將OkHttpCall轉換成rxjava(Scheduler)的寫法。再後來又支持java8(CompletableFuture)甚至居然還有iOS支持。大概就是這樣一個套路。當然我相信square的大神肯定一開始就考慮了這種情況,從而設計了CallAdapter。

適配器模式就是,已經存在的OkHttpCall,要被不同的標準,平台來調用。設計了一個接口CallAdapter,讓其他平台都是做不同的實現來轉換,這樣不花很大的代價就能再兼容一個平台。666。

6.裝飾模式

裝飾模式跟靜態代理很像。

每次一說裝飾模式,就想成decorator,實際上叫wrapper更直觀些。既然是wrapper,那就得有源的句柄,在構造wrapper時得把source作為參數傳進來。wrapper了source,同樣還wrapper其他功能。

代理模式,Proxy Delegate,實際上Delegate也不知道自己被代理了,Proxy偽裝成Delegate來執行,既然是proxy,那proxy不應該提供delegate沒有的public方法,以免被認出來。

拋開理論的描述,我們直接來看下麵的代碼。

你可以將ExecutorCallbackCall當作是Wrapper,而真正去執行請求的源Source是OkHttpCall。之所以要有個Wrapper類,是希望在源Source操作時去做一些額外操作。這裏的操作就是線程轉換,將子線程切換到主線程上去。

分析好文

Retrofit分析-漂亮的解耦套路
Retrofit分析-經典設計模式案例
Android主流網絡請求開源庫的對比(Android-Async-Http、Volley、OkHttp、Retrofit)
Android:手把手帶你深入剖析 Retrofit 2.0 源碼

最後更新:2017-10-25 10:33:54

  上一篇:go  談談App的統一跳轉和ARouter
  下一篇:go  數據庫勒索事件頻發,應該如何確保不被入侵勒索?