Android知识Android开发Android技术知识

造轮子之 Android 多线程多任务断点续传下载器(设计篇)

2017-03-17  本文已影响787人  VitaminChen

前段时间面试,被问到 app 的自动更新是怎么做的,文件下载怎么实现的?用了多线程吗?是否支持断点续传?一下蒙逼,因为直接用第三方框架实现的文件下载,这些问题完全没想过。
回来后觉得这里面其实涉及很多知识点,就打算自己动手封装一个支持多线程多任务断点续传的库,用了一个星期的业余时间,目前主要功能基本完成,所以记录一下这个过程中遇到的问题和收获

1. 涉及知识点

听起来并不复杂的一个功能,但是实际动手做起来发现还是涉及了很多知识点的,概括一下主要涉及以下几个部分:

2. 整体设计

1. 下载请求的封装
用一个 JavaBean 类封装一个下载请求,包括了下载的 url 地址,文件保存的目录以及文件名,基本的参数其实就这三个,还可以根据需要添加更多的配置参数,比如指定并发的线程数等。如果参数比较多的情况,可以使用 Builder 模式

2. 任务的调度
回想我们使用迅雷下载时,填入下载链接,选好保存路径之后点击开始,任务就自动开始下载了。如果此时任务数已经达到上限,那么就会等待,直到有任务结束,再自动开始等待的任务。仔细思考之后,有以下几个要点:

3. 下载任务的执行
一个支持多线程断点续传的任务开始后,其实是分成了串行的两步执行的:
(1) 发起一次 HTTP 请求,获取下载文件的长度信息
(2) 根据文件的长度以及设置的线程数N,把下载任务分成N个子任务,每个子任务再分别发起HTTP请求,负责下载自己那一部分的数据并写入同一个文件中(RandomAccessFile 已经处理了同步问题)。
所以这里我的设计是先使用一个 AsyncTask 获取文件长度,再异步的回调里,开启N个子任务线程进行下载。
这里当然是使用线程池来执行子任务了,子任务都实现 Runnable 丢到线程池里。另外由于 AsyncTask 默认的实现是串行的,也可以让 AsyncTask 在默认的线程池上执行,这样就可以实现多个任务同时开始下载了。

4. 下载任务的封装和管理
首先要用一个类来描述一个下载任务,这个类的设计要考虑以下几点:

然后就是需要一个集合来保存所有的已添加的任务,因为各种对任务的操作,比如暂停,取消,删除等都是要根据ID来找到一个对应任务,所以使用Map来保存可以保证查找的效率。

5. 事件发布设计
所有的事件都通过 LocalBroadcastManager 发布,然后使用者可以有两种方法实现对事件的监听,一种是定义自己的 Receiver 接收处理各种广播事件。还有一种是注册 Listener,然后我们在框架内部实现一个 BroadcastReceiver,根据不同的事件调用 Listener 的不同的方法,这样封装的更好,不过某些场景自己注册Receiver还是更灵活一些,可以在 switch 里面对多个 case 合并处理

6. 任务状态的切换
最主要的部分就是如何暂停或者取消一个正在进行的任务。在下载的子任务线程里,会有一个循环从InputStream读取数据并写入文件的操作,我们就在这个循环这里加入对任务状态的判断,当状态是Downloading时,就继续下载,当状态被设为 Paused 时,就跳出循环,这样就实现了任务的暂停。
当然也可以用 FutureTask.cancel(),在循环里判断 isInterrupted() 来实现,不过因为我们已经有了一个表示任务状态的字段,直接使用这个字段可以达到同样的效果。
当恢复一个暂停的任务时,不能让它直接开始,要把重新加到任务队列里面去,然后等待调度。因为可能已经达到任务上限,所以还是要重新拿到信号量才可以开始。

7. 任务的持久化
不考虑大量任务管理的场景的话,可以直接用 SharedPreference 配合 Gson 的序列化和反序列化,实现任务的持久化。用数据库的话就麻烦一点,要自己读写各个字段,当然也可以用 GreenDao,Realm 等orm框架,不过作为一个实验性项目,这块暂时先不做那么复杂吧。

3. 总结

初步的分析结束,整体的思路已经清楚了,主要的难点应该是在任务的调度,多线程的协作和同步。最后从用户的角度总结一下最终要实现的功能:

下一篇就写具体的代码实现。

上一篇 下一篇

猜你喜欢

热点阅读