Android - OkHttp实现的断点续传

由于阿里大佬的对象存储OSS服务安卓端没有断点续传的功能,看了一下IOS端的实现方案,结合网上的资料整理和实现了安卓端的断点续传。

参考 developer.aliyun.com/article/630…

关键要点

在发起HTTP请求中加入Range请求头

  • Range: bytes=100- 从 101 bytes 之后开始传,一直传到最后。
  • Range: bytes=100-200 指定开始到结束这一段的长度,记住 Range 是从 0 计数 的,所以这个是要求服务器从 101 字节开始传,一直到 201 字节结束。这个一般用来特别大的文件分片传输,比如视频。
  • Range: bytes=-100 如果范围没有指定开始位置,就是要服务器端传递倒数 100 字节的内容。而不是从 0 开始的 100 字节。
  • Range: bytes=0-100, 200-300 也可以同时指定多个范围的内容,这种情况不是很常见。

关键代码

  1. 下载前校验准备
/**
 * 获取下载内容的大小
 * @param downloadUrl 文件地址
 * @return 文件字节长度
 */
private fun getContentLength(downloadUrl: String): Long {
    val request = Request.Builder().url(downloadUrl).build()
    try {
        val response = client.newCall(request).execute()
        if (response != null && response.isSuccessful) {
            val contentLength = response.body()?.contentLength() ?: 0L
            response.body()?.close()
            return contentLength
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
    return 0L
}

/**
 * 下载请求
 * @param fileName 文件名称
 * @param sourceFileName 文件名称
 * @param fileUrl 文件完整地址
 */
private fun downLoadFile(sourceFileName: String,fileName:String, fileUrl: String) {
    //已经下载的文件长度,初始化为0
    //(续载的关键点 = 每次下载的起始位置)
    var downloadLength = 0L
    //下载路径 + 文件名 在本地创建一个文件
    val file = File(downloadFileCacheDir + sourceFileName)
    if (file.exists()) {
        //如果文件存在,得到文件大小
        downloadLength = file.length()
    }
    //得到下载内容的大小
    val contentLength = getContentLength(fileUrl)
    if (contentLength == 0L) {
        //从内存中移除下载任务
        ossFileMap.remove(fileName)
        downLoadTaskMap.remove(fileName)
        onListenerCallBackError(ErrorCode.ERROR_DATA_NULL, "文件错误,长度为0")
        return
    } else if (contentLength == downloadLength) {
        //已下载字节和文件总字节相等,说明已经下载完成了
        ossFileMap.remove(fileName)
        downLoadTaskMap.remove(fileName)
        onListenerCallBackProcess(fileName, contentLength, contentLength)
        onListenerCallBackSuccess(fileName)
        return
    }

    val request = Request.Builder()
            .url(fileUrl)
            .addHeader("RANGE", "bytes=${downloadLength}-")//续传关键(请求头增加)
            .build()
    Logger.d("Down下载:Range = $downloadLength")
    val call = client.newCall(request)
    //加入传载队列
    downLoadTaskMap[fileName] = call
    saveFile(call,file, downloadLength, fileName, contentLength)
} 
  1. 写入文件
/**
 * 写入文件到本地
 * @param call 请求回调
 * @param file 需要写入的本地文件
 * @param downloadLength 已下载文件长度
 * @param contentLength 文件总长度
 */
private fun saveFile(call: Call, file: File?, downloadLength: Long, fileName: String, contentLength: Long) {
    var inputStream: InputStream? = null
    var savedFile: RandomAccessFile? = null
    val response = call.execute()
    try {
        if (response != null) {
            inputStream = response.body()?.byteStream()
            savedFile = RandomAccessFile(file, "rw")
            //跳过已经下载的字节
            savedFile.seek(downloadLength)
            //每次缓存字节数
            val byteArray = ByteArray(1024)
            var total = 0
            var len: Int = -1
            while (inputStream?.read(byteArray)?.also { len = it } != -1) {
                //执行缓存前都需判断该任务是否被暂停
                if (call.isCanceled) {
                    Logger.d("Down:${fileName}下载任务已取消")
                    return
                } else {
                    total += len
                    savedFile.write(byteArray, 0, len)
                    //本次下载总长
                    val currentSize = total + downloadLength
                    //回调进度
                    onListenerCallBackProcess(fileName, currentSize, contentLength)
                    //长度相等则返回成功
                    if (currentSize == contentLength) {
                        response.body()?.close()
                        Logger.d("Down:success")
                        //文件与本地路径字典记录到数据库
                        filesLocalService.saveDownPathData(DownLoadFilesValue().apply {
                            this.fileName = fileName
                            this.filePath = file?.path
                        })
                        ossFileMap.remove(fileName)
                        downLoadTaskMap.remove(fileName)
                        //删除本地传载列表
                        deleteFormLocal(fileName)
                        //成功回调
                        onListenerCallBackSuccess(fileName)
                    }
                }
            }
            response.body()?.close()
        }
    } catch (e: Exception) {
        //发生异常时关闭该下载任务
        ossFileMap.remove(fileName)
        downLoadTaskMap.remove(fileName)
        //失败回调
        onListenerCallBackError(ErrorCode.ERROR_EXCEPTION, e.message)
    } finally {
        try {
            inputStream?.close()
            savedFile?.close()
        } catch (e: Exception) {
            e.printStackTrace();
        }
    }
} 

最后

按照国际惯例,给大家分享一套十分好用的Android进阶资料:《全网最全Android开发笔记》。

整个笔记一共8大模块、729个知识点,3382页,66万字,可以说覆盖了当下Android开发最前沿的技术点,和阿里、腾讯、字节等等大厂面试看重的技术。

图片

图片

因为所包含的内容足够多,所以,这份笔记不仅仅可以用来当学习资料,还可以当工具书用。

如果你需要了解某个知识点,不管是Shift+F 搜索,还是按目录进行检索,都能用最快的速度找到你要的内容。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照整个知识体系编排的。

(一)架构师必备Java基础

1、深入理解Java泛型

2、注解深入浅出

3、并发编程

4、数据传输与序列化

5、Java虚拟机原理

6、高效IO

……

图片

(二)设计思想解读开源框架

1、热修复设计

2、插件化框架设计

3、组件化框架设计

4、图片加载框架

5、网络访问框架设计

6、RXJava响应式编程框架设计

……

图片

(三)360°全方位性能优化

1、设计思想与代码质量优化

2、程序性能优化

  • 启动速度与执行效率优化
  • 布局检测与优化
  • 内存优化
  • 耗电优化
  • 网络传输与数据储存优化
  • APK大小优化

3、开发效率优化

  • 分布式版本控制系统Git
  • 自动化构建系统Gradle

……

图片

(四)Android框架体系架构

1、高级UI晋升

2、Android内核组件

3、大型项目必备IPC

4、数据持久与序列化

5、Framework内核解析

……

图片

(五)NDK模块开发

1、NDK开发之C/C++入门

2、JNI模块开发

3、Linux编程

4、底层图片处理

5、音视频开发

6、机器学习

……

图片

(六)Flutter学习进阶

1、Flutter跨平台开发概述

2、Windows中Flutter开发环境搭建

3、编写你的第一个Flutter APP

4、Flutter Dart语言系统入门

……

图片

(七)微信小程序开发

1、小程序概述及入门

2、小程序UI开发

3、API操作

4、购物商场项目实战

……

图片

(八)kotlin从入门到精通

1、准备开始

2、基础

3、类和对象

4、函数和lambda表达式

5、其他

……

图片

好啦,这份资料就给大家介绍到这了,有需要详细文档的小伙伴,可以微信扫下方二维码免费领取哈~

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
框架提供了一个Interceptor--ProgressInterceptor,可以实现断点续传的功能。在该Interceptor的拦截器中,我们可以通过添加Range请求头来告诉服务器续传的起始位置,同时将服务端返回的数据写入到断点下载文件的指定位置即可。以下是示例代码: ``` public class ProgressInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = chain.proceed(request); return response.newBuilder() .body(new ProgressResponseBody(response.body())) .build(); } private static class ProgressResponseBody extends ResponseBody { private final ResponseBody responseBody; private BufferedSource bufferedSource; public ProgressResponseBody(ResponseBody responseBody) { this.responseBody = responseBody; } @Override public MediaType contentType() { return responseBody.contentType(); } @Override public long contentLength() { return responseBody.contentLength(); } @Override public BufferedSource source() { if (bufferedSource == null) { bufferedSource = Okio.buffer(source(responseBody.source())); } return bufferedSource; } private Source source(Source source) { return new ForwardingSource(source) { long totalBytesRead = 0L; @Override public long read(Buffer sink, long byteCount) throws IOException { long bytesRead = super.read(sink, byteCount); totalBytesRead += bytesRead != -1 ? bytesRead : 0; return bytesRead; } }; } } } ``` 在调用OkHttpClient的Builder方法时添加一个ProgressInterceptor拦截器即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值