https://mp.weixin.qq.com/s/bopDUFMB7EiK-MhLc3KDXQ

essyan 鸿洋 2017-06-29

本文作者

本文由jessyan投稿。

jessyan的博客地址:

http://www.jianshu.com/u/1d0c0bc634db

1

前言

发表上篇文章 我一行代码都不写实现Toolbar!你却还在封装BaseActivity? 已是一个月前的事情~

上篇文章的研究成果让 MVPArms 具备了 监听整个 App 所有 Activity 以及 Fragment 的生命周期(包括三方库),并可向其生命周期内插入代码 的功能,这次我又拿着最近的另一项研究成果向大家汇报,当然同样也是 MVPArms 上的新增功能。

本文提到的MVPArms地址为:

https://github.com/JessYanCoding/MVPArms

Github : 你的 Star 是我坚持的动力 ✊

https://github.com/JessYanCoding/ProgressManager

2

罗列需求

上传下载是大多数 APP 必备的功能,显示进度条也是提高用户体验的重要一环,当然作为 可配置化集成框架 MVPArms 的作者,我想再次提高开发者的使用体验以及开发效率,那我就必须提供一套解决方案。

于是我打开 Github 简单的搜了一圈与 Retrofit , Okhttp , Glide 有关的进度监听库,库到是不少,但是都没有达到我想要的需求,于是我卷起衣袖,准备撸一个,当然,开撸之前要先简单梳理下自己的需求

  1. 这个库一定要支持多个平台,Okhttp , Retrofit , Glide 这三个必须同时支持

  2. 虽然支持这三个库,但是库里面并不能包含这三个库,让用户自己去引入,减小库的体积

  3. 使用一定要简单!!!,最好能一行代码搞定

  4. 侵入性低,并不需要改之前写好的网络请求代码,引入与不引入这个库,对之前的代码都不能有任何影响

  5. 低耦合,用户做网络请求的代码,一定不能和进度接收端的代码有太多关联

  6. 在 App 的任何位置都能接受到某个网络请求的 进度信息

  7. 不仅仅需要满足,一个数据源对应一个进度接收端的一对一关系,还需要满足一个数据源对应多个进度接收端的,一对多关系,这样就可以同步更新多个不同位置的进度条

  8. 默认运行在主线程,让使用者少去切换线程的烦恼

3

需求分析及调研

爽一下子,写出了这么多需求,当产品经理就是一个字爽!

仔细一看这8个需求,瞬间懵逼了,妹的这不是坑自己吗?除了最后一项,我知道可以用 Handler 来实现,其他完全没思路啊,得了,作为一个优质男青年我得知难而进啊,先从第一个需求开始分析吧!

需求 1 (多平台支持)

写之前翻了下 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 替换成重写后的。

4

如何识别需要监听进度的请求?

替换是简单,但是不是每个请求都需要监听上传和下载进度,不可能每个请求都替换啊,开始我想到的是给需要监听进度的请求生成个标记,然后在 Interceptor 中解析到这个标记,就说明这个请求需要监听上传或下载进度,然后就开始替换之

于是我想到最简单的方式就是在请求的时候加一个自定义的 Header ,这样就不用再定义其他的类, Interceptor 遍历所有 Header 发现有这个自定义 Header ,就可以替换。

但是这样并没有解决需求 4,因为这样让用户比平时请求时多了个操作,如果想让之前的代码具有进度监听功能,就要一个个挨着改,增加了劳动量,而且这个操作是针对于我这个库而产生的,当用户并不想使用这个库的时候,会牵扯到修改之前的代码,这样还增加了侵入性。

Url 作为标记

 

一个念头一闪而过,还要什么标记, Url 是唯一的, 不就可以作为标记吗!!!

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

借用 EventBus 思想

为什么把这三个需求放在一起呢,因为这三个需求让我想到了 EventBus ,多个观察者使用同一个标记将自己注册进一个容器,被观察者使用这个标记 Post 一个事件,然后从这个容器中拿出所有使用这个标记注册过的观察者,挨个通知,这样既解耦,并且只要知道这个标记,在 App 任何位置都可以监听,也支持一对多。

加上需求 4,中提到的使用 Url 作为标记,那我就可以做到之前请求的代码一个也不用改,只用写接收端的代码即可实现以上的需求。

5

构思 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 中调用 监听器 的方法就可以了。

6

框架细节优化

无需手动注销

大家都知道 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 一起传给使用者。

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

如果你有想学习的文章直接留言,我会整理征稿。如果你有好的文章想和大家分享欢迎投稿,直接向我投递文章链接即可。

欢迎长按下图->识别图中二维码或者扫一扫关注我的公众号:

一行代码实现Okhttp,Retrofit,Glide下载上传进度监听的更多相关文章

  1. Android OkHttp + Retrofit 下载文件与进度监听

    本文链接 下载文件是一个比较常见的需求.给定一个url,我们可以使用URLConnection下载文件. 使用OkHttp也可以通过流来下载文件. 给OkHttp中添加拦截器,即可实现下载进度的监听功 ...

  2. Android OkHttp文件上传与下载的进度监听扩展

    http://www.loongwind.com/archives/290.html 上一篇文章介绍了用Retrofit实现文件的上传与下载,但是我们发现没办法监听上传下载的进度,毕竟我们在做开发的时 ...

  3. RxHttp 完美适配Android 10/11 上传/下载/进度监听

    1.前言 随着Android 11的正式发布,适配Android 10/11 分区存储就更加的迫切了,因为Android 11开始,将强制开启分区存储,我们就无法再以绝对路径的方式去读写非沙盒目录下的 ...

  4. WP8.1 Study17:网络之后台下载/上传及HttpClient

    一.后台下载/上传 1.简介 使用BackgroundTransferGroup可以十分方便操作上传及下载文件,BackgroundDownloader和BackgroundUploader类中的方法 ...

  5. Android必知必会-使用okhttp的PUT方式上传文件

    注:如果移动端排版有问题,请看 简书版 (<-点击左边),希望CSDN能更好的支持移动端. 背景 公司的文件上传接口使用PUT协议,之前一直用的都是老项目中的上传类,现在项目中使用了okhttp ...

  6. OkHttp实现文件上传进度

    文件上传就一个没刻度的进度条在那里转怎么行,本篇带你实现上传进度,为你的进度条添加刻度吧,啥都不说了,重点重写RequestBody,看代码 import com.squareup.okhttp.*; ...

  7. Retrofit实现图文上传至服务器

    Retrofit实现图文上传至服务器 前言:现在大多数的项目中都涉及图片+文字上传了,下面请详见实现原理: 开发环境:AndroidStudio 1.引入依赖: compile 'com.square ...

  8. https 协议下服务器根据网络地址下载上传文件问题

    https 协议下服务器根据网络地址下载上传文件遇到(PKIX:unable to find valid certification path to requested target 的问题) 使用h ...

  9. git下载/上传文件提示:git did not exit cleanly

    问题:git操作下载/上传文件,提示信息如下 TortoiseGit-git did not exit cleanly (exit code 1) TortoiseGit-git did not ex ...

随机推荐

  1. ElasticStack系列之十一 & 同步 mysql 数据的实践与思考

    问题 1. jdbc-input-plugin 只能实现数据库的追加,对于 elasticsearch 增量写入,但经常 jdbc 源一端的数据库可能会做数据库删除或者更新操作.这样一来数据库与搜索引 ...

  2. Shell记录-Shell脚本基础(六)

    watch是一个非常实用的命令,基本所有的Linux发行版都带有这个小工具,如同名字一样,watch可以帮你监测一个命令的运行结果,省得你一遍遍的手动运行. 1.命令格式 watch[参数][命令] ...

  3. 二分算法的应用——Codevs 1766 装果子

    #include<iostream> #include<cstdio> using namespace std; + ; typedef long long LL; LL a[ ...

  4. 牛市必备的三个条件,A股现在还差几个

    1.国家政策 2.中美贸易 3.资金支持 A股变化如神! 自本月10日受美股大跌的影响后,A股先是随之震荡跳水,千股跌停:随后因高层力挺和政策支持而V型反转,集体涨停:接着上演过山车走势,有时涨得令人 ...

  5. ETL 自动化测试框架

    分享个自己最近在做的自动化测试框架架构图. 数据的测试,入口一般定时任务.可添加参数选择执行任务的范围,也可以选择默认的执行范围.验证测试的为etl测试.数据库中的字段校验(通过对应关系.接口或者SQ ...

  6. Eclipse改变相同代码高亮颜色

    一.点击某一代码时,让相同代码高亮显示(Eclipse默认是这样的) Window ->preferences ->Java ->Editor ->Mark Occurrenc ...

  7. Sql Server 逻辑文件 '' 不是数据库 '' 的一部分。请使用 RESTORE FILELISTONLY 来列出逻辑文件名。

    当使用语句还原数据库时,报如下错误: 消息 3234,级别 16,状态 2,第 29 行逻辑文件 'LenborMealOrder_Base_2017' 不是数据库 'Members_01' 的一部分 ...

  8. shell if判断中常用的a-z表达式含义

    shell if判断中常用的a-z表达式含义 可通过在在linux中man test命令查看下列参数的详细用法   [ -a FILE ] 如果 FILE 存在则为真. [ -b FILE ] 如果 ...

  9. 20165320 实验一 java环境的熟悉

    实验内容与步骤 一.java开发环境的熟悉 1.建立一个有关自己学号的目录 2.在当前文件下编译一个带包Hello.java文件 3.代码内容 package sq; import java.util ...

  10. 与JavaWeb有关的故事(Web请求与Java IO)

    作为一名后端屌丝程序员,对算法.并发.性能乐此不疲.但是,随着年龄和阅历的增加,显然叶落而不知秋的心态是不太能混了.尤其是,某T面试官在明知我是后端,且明确表示对HTTP协议不太熟的情况下,强行让我解 ...