https://mp.weixin.qq.com/s/bopDUFMB7EiK-MhLc3KDXQgit
本文做者github
本文由jessyan投稿。web
jessyan的博客地址:數組
http://www.jianshu.com/u/1d0c0bc634db安全
1
上篇文章的研究成果讓 MVPArms 具有了 監聽整個 App 全部 Activity 以及 Fragment 的生命週期(包括三方庫),並可向其生命週期內插入代碼 的功能,此次我又拿着最近的另外一項研究成果向你們彙報,固然一樣也是 MVPArms 上的新增功能。網絡
本文提到的MVPArms地址爲:多線程
https://github.com/JessYanCoding/MVPArms框架
Github : 你的 Star 是我堅持的動力 ✊ide
https://github.com/JessYanCoding/ProgressManagerpost
因而我打開 Github 簡單的搜了一圈與 Retrofit , Okhttp , Glide 有關的進度監聽庫,庫到是很多,可是都沒有達到我想要的需求,因而我捲起衣袖,準備擼一個,固然,開擼以前要先簡單梳理下本身的需求
這個庫必定要支持多個平臺,Okhttp , Retrofit , Glide 這三個必須同時支持
雖然支持這三個庫,可是庫裏面並不能包含這三個庫,讓用戶本身去引入,減少庫的體積
使用必定要簡單!!!,最好能一行代碼搞定
侵入性低,並不須要改以前寫好的網絡請求代碼,引入與不引入這個庫,對以前的代碼都不能有任何影響
低耦合,用戶作網絡請求的代碼,必定不能和進度接收端的代碼有太多關聯
在 App 的任何位置都能接受到某個網絡請求的 進度信息
不只僅須要知足,一個數據源對應一個進度接收端的一對一關係,還須要知足一個數據源對應多個進度接收端的,一對多關係,這樣就能夠同步更新多個不一樣位置的進度條
默認運行在主線程,讓使用者少去切換線程的煩惱
3
爽一會兒,寫出了這麼多需求,當產品經理就是一個字爽!
仔細一看這8個需求,瞬間懵逼了,妹的這不是坑本身嗎?除了最後一項,我知道能夠用 Handler 來實現,其餘徹底沒思路啊,得了,做爲一個優質男青年我得知難而進啊,先從第一個需求開始分析吧!
寫以前翻了下 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 實現,如同吃了定心丸,瞬間舒坦。
這個需求 Google 了下,也很是簡單,用 provided 引入依賴框架,打包時引入的框架就不會包含進去。
對於這種對外 Api 設計上的需求,咱們應該把主體功能實現了,再慢慢優化到想達到的目標因此先分析下面的需求。
由於需求 1 已經提到,實現上傳和下載進度監聽的關鍵就是,在 Interceptor 中將每一個請求原有的 RequestBody 和 ResponseBody 替換成重寫後的。
因而我想到最簡單的方式就是在請求的時候加一個自定義的 Header ,這樣就不用再定義其餘的類, Interceptor 遍歷全部 Header 發現有這個自定義 Header ,就能夠替換。
可是這樣並無解決需求 4,由於這樣讓用戶比平時請求時多了個操做,若是想讓以前的代碼具備進度監聽功能,就要一個個挨着改,增長了勞動量,並且這個操做是針對於我這個庫而產生的,當用戶並不想使用這個庫的時候,會牽扯到修改以前的代碼,這樣還增長了侵入性。
一個念頭一閃而過,還要什麼標記, Url 是惟一的, 不就能夠做爲標記嗎!!!
爲何把這三個需求放在一塊兒呢,由於這三個需求讓我想到了 EventBus ,多個觀察者使用同一個標記將本身註冊進一個容器,被觀察者使用這個標記 Post 一個事件,而後從這個容器中拿出全部使用這個標記註冊過的觀察者,挨個通知,這樣既解耦,而且只要知道這個標記,在 App 任何位置均可以監聽,也支持一對多。
加上需求 4,中提到的使用 Url 做爲標記,那我就能夠作到以前請求的代碼一個也不用改,只用寫接收端的代碼便可實現以上的需求。
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 的全部 監聽器,調用 監聽器 的監聽方法,並傳入進度信息,就能夠執行使用者的更新邏輯,這就大功告成了。
這個很簡單,使用 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() ,數組分配的是連續的內存區域而且長度是固定的,因此索引效率佔有優點,則使用數組來遍歷,因爲數組長度是固定的,因此也不會出現遍歷時長度變化的問題。
由於 App 用戶可能在前一個進度還沒上傳或下載完的狀況下,繼續使用同一個 Url 開始新的請求,若是框架使用者在上層不去作去除重複點擊的操做。
那同一個 Url 就會同時存在多個正在執行的進度更新,這時就須要有標識符來區分究竟是哪一個進度信息(這個 Url 的全部正在執行的進度更新都會調用以前以這個 Url 註冊過的監聽器),因此我在 Body ,建立時會將 System.currentTimeMillis() 做爲惟一 ID ,保存起來,每次將進度信息和 Id 一塊兒傳給使用者。
7
其實這個庫原本就比較簡單,實現的核心方式在不少地方都是能複製粘貼到的,但通過我這麼一封裝仍是要比以前的方式,簡單優雅很多,而寫這篇文章的目也是想分享下:
如何分析需求,以及如何封裝優化一個小型的庫,固然平時也要多閱讀源碼,不斷積累和借鑑優秀的思想在創做時靈感纔會源源不斷,好比我這個庫就是借鑑的 EventBus 的思想,在寫代碼時要勇於想勇於嘗試較於以前不一樣的新思想,纔會不斷進步。
Github : 具體實現還得看源碼不是? 記得給 Star ✊ 感謝!
https://github.com/JessYanCoding/ProgressManager
Hello 我叫Jessyan,若是您喜歡個人文章,能夠在如下平臺關注我
GitHub: https://github.com/JessYanCoding
掘金: https://gold.xitu.io/user/57a9dbd9165abd0061714613
簡書: http://www.jianshu.com/u/1d0c0bc634db
微博: http://weibo.com/u/1786262517
-- The end
若是你有想學習的文章直接留言,我會整理徵稿。若是你有好的文章想和你們分享歡迎投稿,直接向我投遞文章連接便可。
歡迎長按下圖->識別圖中二維碼或者掃一掃關注個人公衆號: