一行代碼實現Okhttp,Retrofit,Glide下載上傳進度監聽

原文地址: http://www.jianshu.com/p/5832c776621fgit

前言

發表上篇文章 我一行代碼都不寫實現Toolbar!你卻還在封裝BaseActivity? 已經是一個月前的事情,當時有人說我是標題黨,也有人不承認個人內容,可是這也不併不妨礙我,兩天奪得掘金當週周榜第一,並被 鴻洋公衆號 轉載,累計閱讀量超過 3萬github

上篇文章的研究成果讓 MVPArms 具有了 監聽整個 App 全部 Activity 以及 Fragment 的生命週期(包括三方庫),並可向其生命週期內插入代碼 的功能,此次我又拿着最近的另外一項研究成果向你們彙報,固然一樣也是 MVPArms 上的新增功能數組

Github : 你的 Star 是我堅持的動力 ✊安全

gif

羅列需求

上傳下載是大多數 APP 必備的功能,顯示進度條也是提升用戶體驗的重要一環,固然做爲 可配置化集成框架 MVPArms 的做者,我想再次提升開發者的使用體驗以及開發效率,那我就必須提供一套解決方案bash

因而我打開 Github 簡單的搜了一圈與 Retrofit , Okhttp , Glide 有關的進度監聽庫,庫到是很多,可是都沒有達到我想要的需求,因而我捲起衣袖,準備擼一個,固然,開擼以前要先簡單梳理下本身的需求網絡

  1. 這個庫必定要支持多個平臺,Okhttp , Retrofit , Glide 這三個必須同時支持
  2. 雖然支持這三個庫,可是庫裏面並不能包含這三個庫,讓用戶本身去引入,減少庫的體積
  3. 使用必定要簡單!!!,最好能一行代碼搞定
  4. 侵入性低,並不須要改以前寫好的網絡請求代碼,引入與不引入這個庫,對以前的代碼都不能有任何影響
  5. 低耦合,用戶作網絡請求的代碼,必定不能和進度接收端的代碼有太多關聯
  6. App 的任何位置都能接受到某個網絡請求的 進度信息
  7. 不只僅須要知足,一個數據源對應一個進度接收端的一對一關係,還須要知足一個數據源對應多個進度接收端的,一對多關係,這樣就能夠同步更新多個不一樣位置的進度條
  8. 默認運行在主線程,讓使用者少去切換線程的煩惱

需求分析及調研

爽一會兒,寫出了這麼多需求,當產品經理就是一個字爽!多線程

仔細一看這8個需求,瞬間懵逼了,妹的這不是坑本身嗎?除了最後一項,我知道能夠用 Handler 來實現,其餘徹底沒思路啊,得了,做爲一個優質男青年我得知難而進啊,先從第一個需求開始分析吧!框架

需求 1 (多平臺支持)

寫以前翻了下 Google 發現,Okhttp 實現上傳下載進度監聽,並不困難,只用重寫 RequestBodyResponseBody ,並配合 Interceptor 將每一個請求原有的 RequestBodyResponseBody 替換,就能夠實現,都是模版代碼,複製粘貼就能夠了,而 Retrofit 底層使用的是 Okhttp,那就也能夠一樣實現進度監聽ide

可是 Glide怎麼實現進度監聽呢? 個人第一反應就是既然 Retrofit 使用 Okhttp 請求網絡就能夠很是容易的實現,那將 Glide 的底層請求框架換成 Okhttp 也能夠實現咯,做爲一個如此牛逼的庫,確定有擴展的方式,因而立刻去翻 Glide 的源碼,印證了本身的想法,發現 Glide 底層是使用的 HttpConenction 去請求網絡,而且這個類時能夠被替換的,趕快 Google 了下post

compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
複製代碼

ok,找到解決方案,可用上面提供的類,將底層請求框架替換爲 Okhttp ,這個框架最核心的地方已經找到實現方式,主要是經過 Okhttp 實現,如同吃了定心丸,瞬間舒坦

需求 2 (減少體積)

這個需求 Google 了下,也很是簡單,用 provided 引入依賴框架,打包時引入的框架就不會包含進去

需求 3 (一行代碼實現)

對於這種對外 Api 設計上的需求,咱們應該把主體功能實現了,再慢慢優化到想達到的目標因此先分析下面的需求

需求 4 (侵入性低)

由於需求 1 已經提到,實現上傳和下載進度監聽的關鍵就是,在 Interceptor 中將每一個請求原有的 RequestBodyResponseBody 替換成重寫後的

如何識別須要監聽進度的請求?

替換是簡單,可是不是每一個請求都須要監聽上傳和下載進度,不可能每一個請求都替換啊,開始我想到的是給須要監聽進度的請求生成個標記,而後在 Interceptor 中解析到這個標記,就說明這個請求須要監聽上傳或下載進度,而後就開始替換之

因而我想到最簡單的方式就是在請求的時候加一個自定義的 Header ,這樣就不用再定義其餘的類, Interceptor 遍歷全部 Header 發現有這個自定義 Header ,就能夠替換

可是這樣並無解決需求 4,由於這樣讓用戶比平時請求時多了個操做,若是想讓以前的代碼具備進度監聽功能,就要一個個挨着改,增長了勞動量,並且這個操做是針對於我這個庫而產生的,當用戶並不想使用這個庫的時候,會牽扯到修改以前的代碼,這樣還增長了侵入性

Url 做爲標記

一個念頭一閃而過,還要什麼標記, Url 是惟一的, 不就能夠做爲標記嗎!!!

需求 5 (低耦合) ,需求 6 (任何位置均可接收),以及 需求 7 (一對多)

借用 EventBus 思想

爲何把這三個需求放在一塊兒呢,由於這三個需求讓我想到了 EventBus ,多個觀察者使用同一個標記將本身註冊進一個容器,被觀察者使用這個標記 Post 一個事件,而後從這個容器中拿出全部使用這個標記註冊過的觀察者,挨個通知,這樣既解耦,而且只要知道這個標記,在 App 任何位置均可以監聽,也支持一對多

加上需求 4,中提到的使用 Url 做爲標記,那我就能夠作到以前請求的代碼一個也不用改,只用寫接收端的代碼便可實現以上的需求

構思 Api

既然談到 EventBus ,那我就用 EventBusApi 來設計,用戶只用一行代碼,傳入一個 標記 和一個 事件 便可實現上傳和下載進度監聽,沒錯 標記 就是 Url , 事件 就是用於獲取進度信息的 監聽器,這樣也就知足了 需求 3 的一行代碼實現的需求

Like this

ProgressManager.post(標記,事件);
複製代碼

用戶調用這一行代碼後,我會將 Url 做爲 Key,監聽器 做爲 value 放入一個全局惟一的 Map

等等?說好一對多的呢?因此這個 value 必須是 List< 監聽器 > ,這樣就知足了一對多的條件了

內部如何通知監聽器?

咱們把全部須要監聽的 Url監聽器 都註冊進了這個容器,那咱們何時該去通知 監聽器 進度信息呢,固然是在 RequestBodyResponseBody 中開始寫入或讀取二進制流的時候,由於只有他們第一時間知道,讀取和寫入的時間,如今只須要把對應 Url 的全部 監聽器 放入他的 Body 中就能夠了

由於 需求 4 中提到,咱們並不知道哪些請求是須要監聽上傳或下載進度,哪些是不須要的,可是如今咱們就能夠經過 Url 來辨別,由於咱們能夠在 Interceptor 中拿到 RequestUrl

以前咱們已經將 Url 做爲 Key 註冊進了容器,若是容器裏面 Contain 這個 Url 那就是說明這個請求,是須要監聽上傳或下載進度的,那咱們就給他替換成重寫後的 Body 並將監聽器傳入,重寫後的 Body 在發生二進制流的 讀取 或 寫入 時不斷的遍歷這個 Url 的全部 監聽器,調用 監聽器 的監聽方法,並傳入進度信息,就能夠執行使用者的更新邏輯,這就大功告成了

需求 8 (主線程執行)

這個很簡單,使用 Handler.post(Runnable)Runnable 中調用 監聽器 的方法就能夠了

框架細節優化

無需手動註銷

你們都知道 EventBus 註冊觀察者後,在不須要接受事件時,須要手動註銷,可是應用到我這個庫中,事件的接收可能不須要這麼嚴謹,因此爲了免去使用者多餘的步驟,我就是使用 WeakHashMap 代替以前的 Map 容器,這個 WeakHashMap 會在 Java虛擬機 回收內存時,找到沒被使用的 Key,將此條目整個移除,因此不須要手動 remove()

加鎖

在上面提到用戶只須要一行代碼,將 Url監聽器 加入容器,可是這行代碼,多是在不一樣線程中被調用的,並且這行代碼內的一些邏輯在多線程中是不安全的,全部這時我須要加入線程鎖,這個對於三方庫很重要,由於你沒法預知一些用戶的操做

向使用者拋出清晰的錯誤

由於我在 需求 2 中已經提到,此庫只會用 provided 引入 Okhttp ,因此 Okhttp 是不會被打進 aar 包裏的,因此若是使用者在本身的項目中沒有引入 Okhttp 是會報 NoClassDefFoundError 這個錯誤的,可是這個錯誤會讓使用者不知道真實的出錯緣由,讓使用者誤覺得是這個庫的致使的,因此我會在庫初始化的時候, Class.forName("okhttp3.OkHttpClient"); 若是找不到 Okhttp 的這個類,說明使用者沒有引入 Okhttp ,而後我會拋出一個解釋很是清晰的錯誤

提升性能

由於上面提到過我會在 Body ,開始讀取或寫入二進制流時,不斷的遍歷全部監聽器並調用它的監聽方法,來達到一對多的同步更新

可是這樣 監聽器 達到必定數量就會出現性能問題,而且在遍歷時,搞很差使用者也會,不斷的添加新的監聽器,在遍歷時改變容器的長度是容易發生錯誤的

因此我在將 List 傳入 Body 時,將這個 List.toArray() ,數組分配的是連續的內存區域而且長度是固定的,因此索引效率佔有優點,則使用數組來遍歷,因爲數組長度是固定的,因此也不會出現遍歷時長度變化的問題

區分同一個Url的多個進度

由於 App 用戶可能在前一個進度還沒上傳或下載完的狀況下,繼續使用同一個 Url 開始新的請求,若是框架使用者在上層不去作去除重複點擊的操做,那同一個 Url 就會同時存在多個正在執行的進度更新,這時就須要有標識符來區分究竟是哪一個進度信息(這個 Url 的全部正在執行的進度更新都會調用以前以這個 Url 註冊過的監聽器),因此我在 Body ,建立時會將 System.currentTimeMillis() 做爲惟一 ID ,保存起來,每次將進度信息和 Id 一塊兒傳給使用者

總結

其實這個庫原本就比較簡單,實現的核心方式在不少地方都是能複製粘貼到的,但通過我這麼一封裝仍是要比以前的方式,簡單優雅很多,而寫這篇文章的目也是想分享下,如何分析需求,以及如何封裝優化一個小型的庫,固然平時也要多閱讀源碼,不斷積累和借鑑優秀的思想在創做時靈感纔會源源不斷,好比我這個庫就是借鑑的 EventBus 的思想,在寫代碼時要勇於想勇於嘗試較於以前不一樣的新思想,纔會不斷進步

Github : 具體實現還得看源碼不是? 記得給 Star ✊ 感謝!

公衆號

掃碼關注個人公衆號 JessYan,一塊兒學習進步,若是框架有更新,我也會在公衆號上第一時間通知你們


Hello 我叫 JessYan,若是您喜歡個人文章,能夠在如下平臺關注我

-- The end

相關文章
相關標籤/搜索