本文首發於微信公衆號——世界上有意思的事,搬運轉載請註明出處,不然將追究版權責任。微信號:a1018998632,交流qq羣:859640274java
連載文章
國慶快結束了,國慶中有六天都在寫文章看代碼還有比我苦逼的嗎(買個慘,哈哈)。這幾天爲項目新增了五個模塊,順便看了看 kotlin 的語法並在項目中簡單的實踐了一下。本文中會講解其中的兩個模塊,剩下的一些會在不久後發佈的下一篇文章中進行講解。linux
- 1.討論——總結前兩週評論中有意義的討論並給予個人解答
- 2.app架構更新——隨着開發的進行,發現第二篇文章中的架構有一些問題,因此在這裏更新一下
- 3.網絡層定製——基於 retrofit 和 okhttp3 定製一個網絡請求層,中間會附加一些原理講解
1、討論
討論1:zsh 對 bash 的支持並非徹底的,若是運行純 bash 有時候會出問題建議不要在服務器上用。android
- 1.這個讀者的建議很是好,上篇文章中我寫了一個 unbunt 環境的初始化腳本,看來這個腳本只能本身在 linux 下開發的時候使用了
討論2:我覺得 aop 是經過 aspectjrt 來實現的 原來是和 Butterknife 相似來實現的git
- 1.在我認知裏面的 aop 能夠簡單的概括成:經過註解的信息在某些方法的先後添加代碼。
- 2.因此 aspectj 也是能夠實現我在前篇文章中說的 aop 日誌的。
- 3.若是讀者瞭解 aspectj 的原理的話就會發現:他也是經過 gradle 插件來將代碼插到註解的方法先後的,只不過這一部分不須要開發者來是實現。
- 4.而項目中本身實現一個這樣的東西一個是爲了可定製性,另外一個就是爲了能瞭解一些技術的原理而不是單單隻會用。
討論3:建議以已完成某個功能模塊或者某篇文章爲版本,建立不一樣的tag,這樣利於食用。(github 上面的 issue)程序員
- 1.這個讀者的建議也很是好,我已經在每次更新文章的 commit 上面加上了 tag ,你們能夠結合這個來看代碼。
2、app架構更新
我想看過本系列第二篇文章的同窗都看過本項目的模塊架構圖。距離寫下本項目的第一行代碼到如今已經差很少三個月過去了,這個過程當中項目中增長了不少模塊,我對大的項目的把握程度也加深了許多,因此這一節我更新一下 app 的架構。github
我接下來就按照圖1開始講解,標了紅色的小模塊表示已經進行過開發的模塊數據庫
- 1.首先從最底層開始,這裏是一些二方庫(本身開發的sdk)、三方庫(開源的sdk)。其餘的全部模塊都能依賴這裏的庫,固然都是單向依賴(A 依賴 B,可是 B 不能依賴 A)
- 2.再向上一層,這裏有兩個大模塊,generate-code 和 internal-base。
- 1.generate-code:這裏放着生成代碼的幾個模塊,好比用 apt 生成代碼的 annotation-progress,又如用 gradle 的 transform 配合 javassist 生成代碼的 invoker。
- 2.internal-base:這裏放着 app 中的全部的底層模塊,例如負責網絡請求的 http 模塊,例如負責圖片加載的 image 模塊,例如複製數據庫的 database 模塊等等。
- 3.在這裏 generate-code 與 internal-base 這兩個大模塊之間能夠互相依賴(注意這裏表示的不是相似 http 與 image 之間能夠互相依賴,由於這樣會產生循環依賴的錯誤)
- 4.這兩個大模塊均可以被更上層的大模塊所依賴,注意這裏是單向依賴,是必須遵照的約定,由於沒有代碼層面的約束
- 3.再向上看,左邊是一個 external-base 大模塊和一個 core 小模塊組成的
- 1.external-base:這個大模塊裏面目前尚未添加小模塊,可是將來應該會添加進去,這裏面裝着的是外部侵入的代碼的封裝,好比 bugly 除了須要添加庫的依賴還須要爲其加一些另外的代碼,又好比一些 android 廠商的 push 方案集成以後須要的適配代碼
- 2.core:這個是一個小模塊,將其單獨放在外面是由於其起一個承上啓下的做用
- 1.這裏面裝着更上層模塊的公共代碼,好比 app 進入時的初始化代碼。
- 2.解決一些底層小模塊之間須要互相引用的問題,好比 http 須要和 image 之間互相引用,此時會形成循環引用的錯誤,此時就將這些代碼放到 core 中進行處理。暫定,在寫下面的時候我發現這個特性可能會形成本模塊依賴過多的問題,後面應該還會繼續拆分這個小模塊
- 3.溝通底層和上層的模塊
- 3.這裏的兩個模塊能夠被同層右邊的 app-plugins 大模塊所依賴,這裏也是單向依賴
- 4.再看同層的右邊,這一個大模塊名爲 app-plugins,裏面的每個小模塊都能被編譯成一個 app。而後其能夠被最頂層的 app-variants 所依賴,最終構建出不一樣功能的 app。
- 5.最頂層就是 app-variants,這個大模塊只能依賴 app-plugins,裏面幾乎不會有什麼代碼,有的就是一個個 gradle 配置,最終會生成不一樣功能的 app。
3、網絡層定製
如今 okhttp + retrofit,也許是一個新項目的標配了,可是不少人都只是在使用這兩個庫的最基本的功能,卻不知這兩個庫能夠經過定製來實現更多的功能。這一節我就來說講如何基於這兩個庫來定製一個大項目的網絡請求層。中間會穿插着一些原理的講解。編程
1.網絡層請求流程
接下來我會按照圖2開始講解 okhttp + retrofit 整個請求流程,待讀者對整個流程有所瞭解以後再講定製的代碼,這樣會事半功倍。後端
- 1.圖中紅色的框是開始部分,咱們就從這裏開始。這裏默認你們都會使用這兩個框架,多餘的東西就再也不贅述了。
- 2.首先咱們在須要請求一個接口的時候會使用 Retrofit 對象調用其 create 方法建立一個 XXXService。咱們看下圖3的代碼:
- 1.能夠看見這裏就是簡單的用了一下動態代理的方式將 XXXService 的每一個接口交給特定的 ServiceMethod 來實現。
- 2.這裏的 ServiceMethod 怎麼來的呢?看36行的 loadServiceMethod 方法,這首先爲了性能會去 serviceMethodCache 中看看是否有 XXXService 某個接口對應的 ServiceMethod,若是沒有的話就用 Builder 模式建立一個。
- 3.回看圖2,建立好了 XXXService 的實現類以後,咱們通常會結合 Rxjava 調用某個接口,讓其返回一個 Observable 對象。由前面的介紹,咱們知道這裏 Observable 實際上是調用 ServiceMethod.adapt(OkhttpCall) 返回的(能夠看圖3的21行),咱們進入這個方法。
- 1.這個方法裏會將調用交給 CallAdapter.adapt(OkhttpCall)
- 2.有些同窗可能知道這個 CallAdapter 是在初始化 Retrofit 的時候被 Retrofit.Builder() 添加的 CallAdapterFactory 建立的。其有幾個具體實現如圖2。
- 3.那麼這裏要選哪個呢?選擇 CallAdapter 的具體邏輯在 ServiceMethod.build 裏面他會調用 ServiceMethod.createCallAdapter 這裏最終會交給 Retrofit.callAdapter 來尋找合適的 CallAdapter。
- 4.那麼3中的具體查找邏輯是什麼呢?這裏我總結一下:
- 1.會對 CallAdapterFactory 進行循環查找,一旦返回一個 CallAdapter 不爲 null 那麼就使用這個。
- 2.具體是否爲 null 的邏輯交給具體的 CallAdapterFactory 去實現。
- 3.由於是順序查找,因此若是列表中有多個匹配項,這裏只取最開始的一個。
- 4.到這裏咱們先不看圖2,通常來講匹配上的 CallAdapterFactory 會是 RxJava2CallAdapterFactory。咱們先研究一下他是怎麼產生一個 Observable 的。
- 1.先看一下圖4,咱們直接看20行,這裏解釋了爲何通常會匹配到 RxJava2CallAdapterFactory 由於咱們的 XXXService 定義接口的時候通常選擇的返回值 都是 Observable 或者有關 Rxjava 的返回值。而後咱們直接看55行,這裏返回了一個 RxJava2CallAdapter,這個就是生成 Observable 的對象。
- 2.接着咱們看圖5,還記得上面的3.1中咱們說的嗎?Observable 就是 CallAdapter.adapt(OkhttpCall) 產生的。這裏就是具體實現。
- 1.能夠看見18行根據接口調用是同步仍是異步會生成兩種不一樣的 Observable。
- 2.而後後面都是根據一些 flag,爲 Observable 添加一些操做符。
- 5.再回到圖2,如今咱們已經有 Observable 了。這裏咱們先跳過圖2中的幾個步驟,直接來到黃色的框,從這裏開始咱們可讓獲得的 Observable 開始運行。對 Rxjava 熟悉的同窗應該知道,一個 Observable 會從操做符流的最頂部開始運行。因此這裏會從咱們前面講到的 RxJava2CallAdapter.adapt 中定義的第一個 Observable 的 subscribe 開始運行。咱們就默認此次接口調用是同步的這樣簡單點,因此會先進入 CallExecuteObservable 中。
- 1.先看圖6,第1行構造這個對象的時候會傳入一個 Call 對象,其實現有不少咱們在這裏能夠默認其爲 OkhttpCall。
- 2.圖6的第5行,是 Observable 開始運行的時候最早調用的方法(有興趣的同窗能夠看看 Rxjava 的源碼解析)。這裏咱們能夠看見13行,其將調用交給了 Okhttp.execute。
- 3.咱們能夠看向圖7的20行,這裏調用了 createRawCall 建立了一個 okhttp3.Call 其具體實現是 RealCall(咱們直接使用 okhttp 的時候也是經過這個請求網絡)。
- 4.在回到圖2中,如圖2所示當調用 RealCall.execute 的時候,就會進入 okhttp 的請求鏈。okhttp 使用了責任鏈模式,將請求穿過圖2中的一個個攔截器,每一個攔截器都負責一個功能。開發者能夠在攔截器鏈的最開始插入本身的攔截器,以實現一些定製操做。
- 5.再回到圖7,okhttp 將數據請求完畢以後會返回一個 okhttp3.Response,這時候會在32行調用43行的 parseResponse 來將解析這個 Response。
- 6.圖7中後面有些代碼看不見了,其實最終 Response 的解析會交給 ServiceMethod.toResponse。而其又會交給 Converter.coverter。這接口的實現類也不少,最多見的應該就是 GsonConverterFactory 提供的 GsonResponseBodyConverter 了。如圖2,咱們通常也是在建立 Retrofit 的時候添加一些 Converter 以供這裏使用。一樣相似 CallAdapter,Converter 的選取也是同樣的策略
- 7.通過以上調用,咱們就有了一個retrofit2.Respons,其內部有一個解析了 body 以後的對象。
- 6.CallExecuteObservable 中調用完畢以後,調用流程通常會交給 BodyObservable,這裏面很簡單,就是將 retrofit2.Respons 中的解析後的 body 交給下一個 Observable 操做符。就這樣順着操做符流最終咱們在 XXXService 中定義的接口的返回值 Observable 的泛型對象就會被傳入到 subscribe 中供外部調用者使用。如圖2中的粉色框。
2.網絡層定製代碼
所謂定製就是在網絡請求流程的各個主要節點中添加本身的代碼實現以達到特殊的需求。通過前面的講解,我想讀者應該對整個網絡層的請求流程有了一個大體的瞭解。這時咱們能夠再看看圖2,能夠看見其中有幾處我綠色的框,這幾個地方就是咱們能夠添加定製代碼的地方。接下來我就會按順序講解一下這幾處的定製代碼是如何實現的。bash
(1)retrofit2.Call的裝飾
咱們按請求順序能夠在圖2中首先看見的是 NewCall.execute 這個框,接下來我就來講說這個能夠怎麼定製。
- 1.按照咱們前面的講解,你們應該知道,若是不作任何定製的話這裏的 NewCall 就是 OkhttpCall,其會返回一個 retrofit.Response。最終會在開發者的 subscribe 裏面返回一個解析了 body 以後的數據結構(這裏就稱爲 ContentData)。有時候咱們會在 subscribe 裏面須要更多的信息,好比在數據轉化過程當中丟失的 head 的信息。
- 2.此時咱們就能夠對 OkhttpCall 進行一個封裝,首先咱們能夠定義一個咱們本身的 DataContainer 對象,其用於封裝 ContentData,而後其還能夠裝數據轉化中丟失的數據。如圖10。
- 3.那麼咱們在定義 XXXService 的接口的返回值的時候就能這樣定義:Observable<DataContainer<ContentData>>。
- 4.此時有人眼尖就會發現,不對啊這個 DataContainer 是被 Gson 反序列化過來的,裏面的 okhttp3.Response 對象服務器又不知道是什麼這樣怎麼序列化呢?
- 5.答案就在圖8,圖9中。你們能夠看圖8的第7行,這裏我添加了一個自定義的 CallAdapterFactory。
- 6.在看圖8的4四、4八、49行,根據前面咱們描述的請求流程,44行的 CallAdapter 會用來生成 Observable。再看48行,這裏的 call 就是 OkhttpCall 了。咱們將其傳入 buildCall 中返回了一個 NewCall,這裏就是關鍵。
- 7.buildCall 的實現代碼在圖9,能夠看38行。這裏的實現很是簡單直接就是將 OkhttpCall 封裝 返回了一個 ContainerCall,如圖11。
- 8.DataContainerCall 裏面的代碼就不用我說了吧,就是給 DataContainer 傳入一個 okhttp3.Response 對象。
- 9.你們是否是以爲就這樣一個小東西很簡單?其實我也以爲很簡單,可是隻要你會用了這一個小東西,那麼更多實用的功能都能被這樣實現。
(2)OkhttpClient定製
按順序下來,第二個定製的地方就是 OkhttpCall 調用 okhttp.RealCall 的地方了。
- 1.咱們看圖8的21行,這裏給 Retrofit 添加了一個 OkhttpClient。以後的請求都是經過它來發送的。
- 2.這裏插一下,你們能夠看看3行,這裏傳的是一個 RetrofitConfig,它實際上是一個接口,像圖9的 DefaultRetrofitConfig 就是它的一個實現。固然咱們還能夠有不一樣的實現以實現不一樣的定製方式。
- 3.那麼咱們仍是再看圖9的6行,能夠看見這個方法的返回值 Builder 中添加了一系列 Intercept。由咱們前面的講解可知,這些是攔截器,而後會按添加的順序攔截請求和響應。
- 4.這裏能夠看見我實現了各類不一樣的功能:打印網絡請求日誌(這個在上一篇文章中沒實現,如今實現了)、過濾過於頻繁的請求(防止ddos攻擊)、SSL認證(固然如今沒有後端還沒實現)、超時攔截、添加自定義的參數等等。
- 5.這裏的定製比較簡單,你們能夠去看看各個攔截器中的實現。
(3)Converter定製
- 1.其實這個也很簡單,你們可能都用過,就是圖8的五、6兩行,添加的數據轉換器。
- 2.你們只要瞭解我前面講解的 Converter 的執行策略就能夠了。
(4)CallAdapter定製
- 1.你們能夠回看 (1)retrofit2.Call的裝飾 這一節,咱們添加了一個 CustomAdapterFactory。
- 2.由於 CustomAdapterFactory 比 RxJava2CallAdapterFactory 先添加,因此其優先級比較高。再看圖8的40行,這裏獲取了一個 delegate,其實就是 RxJava2CallAdapterFactory。因此咱們能夠在 RxJava2CallAdapter 返回的 Observable 上面添加一些統一的操做符。
- 3.具體的代碼在圖8的49行,而後轉到圖9的42行。能夠看見我就只添加了一些簡單的操做符:計數請求成功和失敗次數、配合 ThrottlingInterceptor 進行頻繁請求過濾。
(5)網絡層定製代碼總結
上面就是在網絡請求的四個主要節點進行定製的方式。其實總結起來比較簡單:1是擴展 Retrofit 返回的結果、2是擴展 okhttp 請求和返回、3是解析 okhttp 返回給 Retrofit 的結果、4是加強對 Retrofit 返回結果的處理。
4、總結
不知不覺已經寫了這麼多了,原本覺得還能夠寫一節 Fresco 的定製,如今看來只能放在下一篇文章了。在這裏預告一下:從零開始仿寫一個抖音App這一系列的文章大概還有一到兩篇 android 層面的文章,而且會在接下來的一週左右放出。
這一階段結束以後個人文章和學習重心將會轉向音視頻這塊。這幾個月過來雖然有時候文章會 delay,但最終我也信守承諾沒有棄坑。最後但願你們能持續關注本系列,畢竟我都已經這麼努力了不是:)。
不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限於:科幻、科學、科技、互聯網、程序員、計算機編程。下面是個人微信公衆號:世界上有意思的事,乾貨多多等你來看。