一、实现方式选型
需求:1. 支持多文件上传。2. 大文件上传
目前主流的做法有四种:
线程池
后台Service
前台Service
WorkManager
方案
执行速度
生命周期
特点
线程池
马上执行
很短
用来执行耗时操作,这是比较普遍的做法,但是它比较容易在后台被系统杀死。
后台Service
马上执行
短
后台服务实际上也要用到线程,只是它是一个独立的组件比较容易管理,但是同样也容易被高版本的系统杀死。
前台Service
马上执行
长
在通知栏通知用户在做什么操作,和应用生命周期绑定,比后台Service优先级高,但是不适合后台任务的场景。
WorkManager
延迟执行
很长
优势在于即使应用退出后依然可以执行后台任务,但任务可能会延迟执行。
WorkManager 的特性其实比较诱人,即使杀了应用依然能把后台任务进行到底,但对于可能上传很大文件的应用来说有些流氓了。。而且可能会延迟执行,不适合我们的场景。
前台Service不适合我们后台上传的场景。
线程池生命周期太短,对于这种持续执行I/O操作的场景还是需要更稳定的方式。
后台Service相对来说是比较好的方案,但同样有缺点,就是在8.0以上的系统很容易被杀死,而且系统内存不足的情况下也会优先被杀死。所以需要做好重启Service,有必要的话还可以重启被中断的任务。
选完核心的组件后需要确定如何实现大文件上传,这方面其实并不需要纠结,基本就是切片上传或长连接上传,这里采取的是切片上传的方案。
最后对于多文件上传的处理:
并发上传。
串行上传。
这里选择了串行上传,一个文件上传完后再上传下一个文件。不选择并发上传的原因是即使多线程上传,线程大部分时间还是在处理I/O问题,每个文件分到的带宽会减少,和单一的按顺序上传文件应该相差不大。而且多文件可能也会给服务端带来压力。最后多线程并发也会增加上传完成后回调处理的复杂度。
最后确定的方案是 后台Service+切片上传+串行上传。
二、串行任务队列
由于需要串行上传,因此需要实现串行队列,其实 java 库已经有类似的类 LinkedBlockingQueue,不过我依然需要一些定制的 api 来实现一些功能,所以直接自己简单实现了一个任务队列,先明确下这个队列的特点:
当一个任务在执行时,下一个任务在队列中等待。
当队列中没任务时,子线程一直在等待,直到队列内添加了新任务。
不能重复添加同一个任务。
实现以上功能需要用到锁相关的知识,这里用的是 ReentrantLock 可重入锁:
private val thread: Thread
private var queue = CopyOnWriteArrayList()
private