一行代码实现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

相关文章
相关标签/搜索