原文地址: www.jianshu.com/p/5832c7766…git
前言
發表上篇文章 我一行代碼都不寫實現Toolbar!你卻還在封裝BaseActivity? 已經是一個月前的事情,當時有人說我是標題黨,也有人不承認個人內容,可是這也不併不妨礙我,兩天奪得掘金當週周榜第一,並被 鴻洋公衆號 轉載,累計閱讀量超過 3萬github
上篇文章的研究成果讓 MVPArms 具有了 監聽整個 App 全部 Activity 以及 Fragment 的生命週期(包括三方庫),並可向其生命週期內插入代碼 的功能,此次我又拿着最近的另外一項研究成果向你們彙報,固然一樣也是 MVPArms 上的新增功能數組
Github : 你的 Star 是我堅持的動力 ✊安全
gif
羅列需求
上傳下載是大多數 APP 必備的功能,顯示進度條也是提升用戶體驗的重要一環,固然做爲 可配置化集成框架 MVPArms 的做者,我想再次提升開發者的使用體驗以及開發效率,那我就必須提供一套解決方案網絡
因而我打開 Github 簡單的搜了一圈與 Retrofit , Okhttp , Glide 有關的進度監聽庫,庫到是很多,可是都沒有達到我想要的需求,因而我捲起衣袖,準備擼一個,固然,開擼以前要先簡單梳理下本身的需求多線程
這個庫必定要支持多個平臺,Okhttp , Retrofit , Glide 這三個必須同時支持
雖然支持這三個庫,可是庫裏面並不能包含這三個庫,讓用戶本身去引入,減少庫的體積
使用必定要簡單!!!,最好能一行代碼搞定
侵入性低,並不須要改以前寫好的網絡請求代碼,引入與不引入這個庫,對以前的代碼都不能有任何影響
低耦合,用戶作網絡請求的代碼,必定不能和進度接收端的代碼有太多關聯
在 App 的任何位置都能接受到某個網絡請求的 進度信息
不只僅須要知足,一個數據源對應一個進度接收端的一對一關係,還須要知足一個數據源對應多個進度接收端的,一對多關係,這樣就能夠同步更新多個不一樣位置的進度條
默認運行在主線程,讓使用者少去切換線程的煩惱
需求分析及調研
爽一會兒,寫出了這麼多需求,當產品經理就是一個字爽!框架
仔細一看這8個需求,瞬間懵逼了,妹的這不是坑本身嗎?除了最後一項,我知道能夠用 Handler 來實現,其餘徹底沒思路啊,得了,做爲一個優質男青年我得知難而進啊,先從第一個需求開始分析吧!ide
需求 1 (多平臺支持)post
寫以前翻了下 Google 發現,Okhttp 實現上傳下載進度監聽,並不困難,只用重寫 RequestBody 和 ResponseBody ,並配合 Interceptor 將每一個請求原有的 RequestBody 和 ResponseBody 替換,就能夠實現,都是模版代碼,複製粘貼就能夠了,而 Retrofit 底層使用的是 Okhttp,那就也能夠一樣實現進度監聽性能
可是 Glide怎麼實現進度監聽呢? 個人第一反應就是既然 Retrofit 使用 Okhttp 請求網絡就能夠很是容易的實現,那將 Glide 的底層請求框架換成 Okhttp 也能夠實現咯,做爲一個如此牛逼的庫,確定有擴展的方式,因而立刻去翻 Glide 的源碼,印證了本身的想法,發現 Glide 底層是使用的 HttpConenction 去請求網絡,而且這個類時能夠被替換的,趕快 Google 了下
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
ok,找到解決方案,可用上面提供的類,將底層請求框架替換爲 Okhttp ,這個框架最核心的地方已經找到實現方式,主要是經過 Okhttp 實現,如同吃了定心丸,瞬間舒坦
需求 2 (減少體積)
這個需求 Google 了下,也很是簡單,用 provided 引入依賴框架,打包時引入的框架就不會包含進去
需求 3 (一行代碼實現)
對於這種對外 Api 設計上的需求,咱們應該把主體功能實現了,再慢慢優化到想達到的目標因此先分析下面的需求
需求 4 (侵入性低)
由於需求 1 已經提到,實現上傳和下載進度監聽的關鍵就是,在 Interceptor 中將每一個請求原有的 RequestBody 和 ResponseBody 替換成重寫後的
如何識別須要監聽進度的請求?
替換是簡單,可是不是每一個請求都須要監聽上傳和下載進度,不可能每一個請求都替換啊,開始我想到的是給須要監聽進度的請求生成個標記,而後在 Interceptor 中解析到這個標記,就說明這個請求須要監聽上傳或下載進度,而後就開始替換之
因而我想到最簡單的方式就是在請求的時候加一個自定義的 Header ,這樣就不用再定義其餘的類, Interceptor 遍歷全部 Header 發現有這個自定義 Header ,就能夠替換
可是這樣並無解決需求 4,由於這樣讓用戶比平時請求時多了個操做,若是想讓以前的代碼具備進度監聽功能,就要一個個挨着改,增長了勞動量,並且這個操做是針對於我這個庫而產生的,當用戶並不想使用這個庫的時候,會牽扯到修改以前的代碼,這樣還增長了侵入性
Url 做爲標記
一個念頭一閃而過,還要什麼標記, Url 是惟一的, 不就能夠做爲標記嗎!!!
需求 5 (低耦合) ,需求 6 (任何位置均可接收),以及 需求 7 (一對多)
借用 EventBus 思想
爲何把這三個需求放在一塊兒呢,由於這三個需求讓我想到了 EventBus ,多個觀察者使用同一個標記將本身註冊進一個容器,被觀察者使用這個標記 Post 一個事件,而後從這個容器中拿出全部使用這個標記註冊過的觀察者,挨個通知,這樣既解耦,而且只要知道這個標記,在 App 任何位置均可以監聽,也支持一對多
加上需求 4,中提到的使用 Url 做爲標記,那我就能夠作到以前請求的代碼一個也不用改,只用寫接收端的代碼便可實現以上的需求
構思 Api
既然談到 EventBus ,那我就用 EventBus 的 Api 來設計,用戶只用一行代碼,傳入一個 標記 和一個 事件 便可實現上傳和下載進度監聽,沒錯 標記 就是 Url , 事件 就是用於獲取進度信息的 監聽器,這樣也就知足了 需求 3 的一行代碼實現的需求
Like this
ProgressManager.post(標記,事件);
用戶調用這一行代碼後,我會將 Url 做爲 Key,監聽器 做爲 value 放入一個全局惟一的 Map 中
等等?說好一對多的呢?因此這個 value 必須是 List< 監聽器 > ,這樣就知足了一對多的條件了
內部如何通知監聽器?
咱們把全部須要監聽的 Url 的 監聽器 都註冊進了這個容器,那咱們何時該去通知 監聽器 進度信息呢,固然是在 RequestBody 和 ResponseBody 中開始寫入或讀取二進制流的時候,由於只有他們第一時間知道,讀取和寫入的時間,如今只須要把對應 Url 的全部 監聽器 放入他的 Body 中就能夠了
由於 需求 4 中提到,咱們並不知道哪些請求是須要監聽上傳或下載進度,哪些是不須要的,可是如今咱們就能夠經過 Url 來辨別,由於咱們能夠在 Interceptor 中拿到 Request 的 Url
以前咱們已經將 Url 做爲 Key 註冊進了容器,若是容器裏面 Contain 這個 Url 那就是說明這個請求,是須要監聽上傳或下載進度的,那咱們就給他替換成重寫後的 Body 並將監聽器傳入,重寫後的 Body 在發生二進制流的 讀取 或 寫入 時不斷的遍歷這個 Url 的全部 監聽器,調用 監聽器 的監聽方法,並傳入進度信息,就能夠執行使用者的更新邏輯,這就大功告成了
需求 8 (主線程執行)
這個很簡單,使用 Handler.post(Runnable) 在 Runnable 中調用 監聽器 的方法就能夠了
框架細節優化
無需手動註銷
你們都知道 EventBus 註冊觀察者後,在不須要接受事件時,須要手動註銷,可是應用到我這個庫中,事件的接收可能不須要這麼嚴謹,因此爲了免去使用者多餘的步驟,我就是使用 WeakHashMap 代替以前的 Map 容器,這個 WeakHashMap 會在 Java虛擬機 回收內存時,找到沒被使用的 Key,將此條目整個移除,因此不須要手動 remove()
加鎖
在上面提到用戶只須要一行代碼,將 Url 和 監聽器 加入容器,可是這行代碼,多是在不一樣線程中被調用的,並且這行代碼內的一些邏輯在多線程中是不安全的,全部這時我須要加入線程鎖,這個對於三方庫很重要,由於你沒法預知一些用戶的操做
向使用者拋出清晰的錯誤
由於我在 需求 2 中已經提到,此庫只會用 provided 引入 Okhttp ,因此 Okhttp 是不會被打進 arr 包裏的,因此若是使用者在本身的項目中沒有引入 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 ✊ 感謝!