okdownloader源码分析

作者是流利说的Android架构负责人,本来有一个5k+ star的FileDownloader项目,但是又重新写了这个下载框架okdownloader,具体原因如下:

  • FileDownloader framework is not easy to write unit-test, it is not a testable framework, so it is not stable enough.
  • The core library of FileDownloader is too complex and not pure enough, so 5K+ star 1K+ fork with around 10 PR

相对于FileDownloader的优势

  • 单元测试覆盖度非常高
  • 更简单的接口
  • 支持任务优先级
  • 使用Uri标识文件来存储output-stream
  • 核心代码更加轻量及纯净
  • 更灵活的回调、监听机制
  • 更容易扩展
  • 保持性能的基础上减少线程数量
  • 文件IO线程池独立于网络IO线程池
  • response header中不包含文件名时,自动从URL中获取(使用正则表达式实现)

主框架分析

  • 下载请求通过DownloadTask.Builder封装,通过各种set方法配置请求参数

  • 全部下载策略定义在OkDownload中,如果需要自定义则需要通过内置的Builder构造出一个实例,再通过setSingletonInstance方法设置,替换默认实现

  • 下载请求过程分为两个调用连(connect、fetch),在DownloadChain中分别通过#processConnect以及#processFetch的递归调用来实现

  • 下载文件的分块逻辑实现在BreakpointInterceptor中,当DownloadCall创建新task时,首先将一个空的BlockInfo添加到BreakpointInfo#blockInfoList中并启动对应的DownloadChain,同时park当前线程直到第一个DownloadChain中的BreakpointInterceptor计算好分块数量,之后清空BreakpointInfo#blockInfoList并unpark当前线程,执行后续操作


    下载流程.png
  • 多种下载回调,根据需求来选择DownloadListener1,DownloadListener2,etc..

  • 多任务下载,使用DownloadContext来构造

  • cancel操作不会删除已下载的文件,只相当与暂停,重新下载时会从断点继续下载

    //DownloadCall#execute 任务开始
    void taskStart(DownloadTask task);
    //DownloadCall#execute 从头开始
    void downloadFromBeginning(DownloadTask task, BreakpointInfo info, ResumeFailedCause cause);
    //DownloadCall#execute 从断点开始
    void downloadFromBreakpoint(DownloadTask task, BreakpointInfo info);
    //HeaderInterceptor#interceptConnect 创建链接开始
    void connectStart(DownloadTask task, int blockIndex,
                      @NonNull Map<String, List<String>> requestHeaderFields);
    //HeaderInterceptor#interceptConnect 创建链接结束
    void connectEnd(DownloadTask task, int blockIndex, int responseCode,
                    @NonNull Map<String, List<String>> responseHeaderFields);
    //BreakpointInterceptor#interceptConnect 分块结束
    void splitBlockEnd(DownloadTask task, BreakpointInfo info);
    //DownloadChain#start 开始下载
    void fetchStart(DownloadTask task, int blockIndex, long contentLength);
    //FetchDataInterceptor#interceptFetch->DownloadChain#flushNoCallbackIncreaseBytes 下载进度
    void fetchProgress(DownloadTask task, int blockIndex, long increaseBytes);
    //DownloadChain#start 下载结束
    void fetchEnd(DownloadTask task, int blockIndex, long contentLength);
    //DownloadCall#execute 任务结束
    void taskEnd(DownloadTask task, EndCause cause, @Nullable Exception realCause);

代码结构

.
├── DownloadContext.java //多个下载任务串/并行下载,使用QueueSet来做设置
├── DownloadListener.java //下载状态回调接口定义
├── DownloadMonitor.java
├── DownloadSerialQueue.java
├── DownloadTask.java //单个下载任务
├── OkDownload.java //入口类,负责下载任务装配
├── OkDownloadProvider.java //单纯为了获取上下文Context
├── SpeedCalculator.java //下载速度计算
├── StatusUtil.java //获取DownloadTask下载状态,检查下载文件是否已经下载完成等
├── UnifiedListenerManager.java //多个listener管理
└── core
├── NamedRunnable.java //可命名的线程实现
├── Util.java //工具类
├── breakpoint
│ ├── BlockInfo.java //下载分块信息,记录当前块的下载进度,第0个记录整个下载任务的进度
│ ├── BreakpointInfo.java // BlockInfo聚合类,包含文件名、URL等信息
│ ├── BreakpointStore.java //下载过程中断点信息存储接口定义
│ └── BreakpointStoreOnCache.java //断点信息存储在缓存中的实现
├── cause
│ ├── EndCause.java //结束状态
│ └── ResumeFailedCause.java //下载异常原因
├── connection
│ ├── DownloadConnection.java // 下载链接接口定义
│ └── DownloadUrlConnection.java //下载链接UrlConnection实现
├── dispatcher
│ ├── CallbackDispatcher.java //DownloadListener分发代理(是否回调到UI线程,默认为true)
│ └── DownloadDispatcher.java //下载任务线程分配
├── download
│ ├── DownloadCache.java //MultiPointOutputStream包裹类
│ ├── DownloadCall.java //下载任务线程,包含DownloadTask、DownloadChain的list以及DownloadCache
│ ├── DownloadChain.java //持有DownloadTask、BreakpointInfo、DownloadCache、DownloadConnection等对象,链式调用各connect及fetch的Interceptor,开启下载任务
│ └── DownloadStrategy.java //下载策略,包括分包策略、下载文件命名策略以及response是否可用
├── exception //各种异常
│ ├── FileBusyAfterRunException.java
│ ├── InterruptException.java
│ ├── PreAllocateException.java
│ ├── ResumeFailedException.java
│ ├── RetryException.java
│ └── ServerCancelledException.java
├── file
│ ├── DownloadOutputStream.java //输出流接口定义
│ ├── DownloadUriOutputStream.java //Uri输出流实现
│ ├── MultiPointOutputStream.java //多block输出流管理
│ └── ProcessFileStrategy.java //下载过程中文件处理逻辑
├── interceptor
│ ├── BreakpointInterceptor.java //connect时分块,fetch时循环调用FetchDataInterceptor获取数据
│ ├── FetchDataInterceptor.java //fetch时读写流数据,记录增加bytes长度
│ ├── Interceptor.java
│ ├── RetryInterceptor.java //错误处理、connect时重试机制,fetch结束时同步输出流,确保写入数据完整
│ └── connect
│ ├── CallServerInterceptor.java //启动DownloadConnection
│ ├── HeaderInterceptor.java //添加头信息,调用connectStart、connectEnd
│ └── RedirectInterceptor.java //重定向相关处理
└── listener //多种回调及辅助接口
├── DownloadListener1.java
├── DownloadListener2.java
├── DownloadListener3.java
├── DownloadListener4.java
├── DownloadListener4WithSpeed.java
└── assist
├── Listener1Assist.java
├── Listener4Assist.java
└── Listener4SpeedAssistExtend.java

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,387评论 25 707
  • 俗话说,买卖不成话不到,话语一到卖三俏,由此可见销售语言的重要性。销售人员主要是靠嘴吃饭的,因此,一名出色的销售人...
    相伴五十年阅读 1,126评论 0 0
  • 1 一房 二人 三餐 四季 不为油盐愁 不担心你走 2 屋后靠山 门前有河 山上打猎 下水捉鱼 与你相拥而眠 与鸟...
    吴应阅读 270评论 1 5
  • 每个人的初恋,大都十分纯情。跨过了初恋,爱情就生出了很多姿态。有人变得风流,见一个爱一个;有人冷漠,再不会拿出真心...
    帅也得忍着阅读 180评论 0 1