談談網絡庫和Retrofit
本文目錄如下
- 網絡模塊需要具備什麼能力
- 為什麼Retrofit是個好選擇
- Retrofit業務分析
- Retrofit技術點
- 設計模式
一.網絡模塊需要具備什麼能力?
下麵解釋一下重要的部分.
1.支持緩存
為了讓頁麵快速展現,很多頁麵需要先加載緩存.
通用策略是
1.先加載緩存;
2.然後做網絡請求
3.網絡請求成功後刷新頁麵,並且更新緩存數據
這裏涉及到,緩存要存在哪裏. 一般也是兩種方案:
- 業務側做緩存. 每個頁麵自己維護緩存,一般存在db/sp/文件係統中
- 網絡側做緩存, 業務做網絡請求的時候,可以要求返回緩存數據.
方案1:適用於存儲需要檢索數據的情況. 比如數據存在db中,可以用sql查詢. 缺點是業務側需要實現緩存策略,較麻煩.
方案2: 業務側調用簡單,適用場景非常廣泛.
所以在網路側支持緩存, 是一個很普遍的方案.
2.AccessToken失效後重發失敗請求
這個是基於這幾種前提.
App有登錄功能
App登錄通過 AccessToken/RefreshToken實現
簡單說明 AccessToken失效後會發生什麼:
- AccessToken失效
- 做網絡請求,服務端會返回結果說AccessToken失效
- 客戶端利用RefreshToken 重新換AccessToken.
- 拿著新AccessToken .
- 業務側拿到正確結果
一般AccessToken失效是比較頻繁的, 如果沒有自動重新請求機製, 每次AccessToken失效用戶都能感知到,這樣體驗很不好. 所以一般要在網絡庫/登錄模塊之間或者上層做一個封裝解決這個問題..
3. 多級別取消網絡請求
最好是能:
- 取消一次網絡請求
- 某個頁麵退出時,能取消整個頁麵對應的網絡請求.
資源嗎,能省點就省點
4. 支持"同步/異步"請求
5. 支持選中在"異步線程/UI線程"回調
很多請求是需要需要刷UI的, 這樣就不需要主動post到UI線程了.
二.為什麼Retrofit是個好選擇
Retrofit-Github
選開源庫,有幾個點很重要.
1.開源庫要穩定,優先選擇有人維護,用戶量大的庫
成熟的項目明顯的坑少. 所以一個應用廣泛的庫是一個穩妥的選擇.
Retrofit: Square出品, 2萬+ star,一直在更新
2.開源庫的代碼結構不能太龐大,太複雜.
是代碼總會有bug的, 有問題要能看明白,可以選擇規避或者自行解決. 代碼龐大或者邏輯複雜時,排查問題就是一個很痛苦的過程
Retrofit 代碼量少,結構清晰
3.拓展性好
業務是變化的, 要在預期的業務變化內, 當前庫要能cover住, 如果開發幾版發現不能滿足拓展的需求, 臨時換庫,那會帶來很多工作量和bug
Retrofit 在解耦上做的很棒,拓展性高
4.開源庫自身的依賴庫不能太多
在這點上很重要,引入一個庫如果要一並帶入很多它的依賴庫,會有很大的問題:
1. 會增大包的體積
2. 會讓庫變得不好維護,鏈條上的每個庫都可能有問題.
3. 最大的問題是, 這些依賴庫可能會和App的其他依賴庫衝突. 會產生類衝突,版本衝突. 排查這些衝突是一個很痛苦的過程.(在這點上, 集團的依賴庫這個問題尤為嚴重)
Retrofit目前依賴於Okhttp3, 考慮到OKhttp3也很流行,可以一並引入
三. Retrofit業務分析
做為一個網絡庫Okhttp的封裝, Retrofit需要處理3部分問題:
- 允許業務拓展哪些環節.
- 這些拓展的注冊,和什麼情況下生效
- 拓展是否靈活
1.拓展性
Retrofit允許配置的是 CallAdapter,RequestBodyConverter,ResponseConvert.
CallAdapter: 負責調用OkHttpCall發起請求,處理回調分發
RequestBodyConverter: 在構建Request的時候, 構建body
ResponseConverter: 轉換網絡請求結果.
Retrofit發起網絡請求最終走的的OKHttpCall.
網絡請求的流程套路大家都一樣, 都是:
"業務發起請求"->"構建Request"->"發起請求"->"解釋請求"->"回調業務".
下麵是他的調用流程, 藍色部分是自定義模塊.
2. 功能的注冊和獲取
注冊
在Retrofit初始化的時候, 允許添加CallAdapterFactory和ConverterFactory.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://httpbin.org")
.addCallAdapterFactory(new ErrorHandlingCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build();
獲取
注冊和獲取的流程如下.
按注冊的順序獲取,隻要找到了就就結束查找.
3.靈活的處理能力
每一個請求聲明是一個函數, 他包含3部分信息:
- Return Type
- Method Annotation
- Parameter Annotation
對於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);
方法有三部分信息
- 函數返回值
- 方法體上的Annotaion
- 參數中的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.利用了方法上的各種信息
7.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.動態代理
再來說動態代理。以往的動態代理和靜態代理使用的場景是類似的。都想在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
都用了抽象工廠模式, Interface
和Factory
放到了一個類中,減少類文件的數量, 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}
. 這個Call
是OkHttpCall
,它不能被我們直接使用嗎?被轉換後要去實現什麼特殊的功能嗎?
我們假設下。一開始,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