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

  本篇文章为你整理了Java 断点下载(下载续传)服务端及客户端(Android)代码(java断点续传框架)的详细内容,包含有java文件下载断点续传 java断点续传框架 断点java怎么用 java实现断点续传原理 Java 断点下载(下载续传)服务端及客户端(Android)代码,希望能帮助你了解 Java 断点下载(下载续传)服务端及客户端(Android)代码。

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

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

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

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

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

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

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

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

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

  

Range: bytes=start-end 请求数据的范围区间下标为[start,end),不包含end下标

 

  Range: bytes=0-499 其实就是前500个字符

  Range: bytes=0- 如省略第二个参数,即从索引开始位置,到结束位置

  Range: bytes=-500 如省略地一个参数,即表示最后500个字符

  

 

  上面的传统的模式,除此之外还有多个区间集合:

  

Range: bytes=p1-p2,m1-m2

 

  

 

  下文中给出的例子并没有实现对此格式的支持,有需求的可以加上(处理逗号即可)

  我们根据上面的格式,服务端对Range字段进行处理(String字符串数据处理),在响应头中返回对应的参数content-length,content-range,然后在对应的文件流

  content-length比较好理解,实际上就是本次传输的文件大小,其长度要和返回的数据流的长度相等,否则会出现错误

  Content-Range格式如下:

  

Content-Range: bytes first-end/total

 

  

 

  后面的first end total三个是变量数据,需要我们来进行拼接,下面举几个例子

  我们有一个文件,大小为5000

  下面表格则是列出对应客户端传参及服务端响应的数据:

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

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

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

  我所说的客户端是指的Android客户端,由于App开发也是基于Java,所以也是可以使用RandomAccessFile这个类(至于网页H5等其他语言该如何实现断点续传功能,各位就得查阅了对应的资料)

  对于客户端:有以下逻辑:

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

  对于服务端:有以下逻辑:

  处理客户端Range的参数,返回对应的content-range和content-length,通过RandomAccessFile将客户端要求的文件的范围数据传回个服务端

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

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

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

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

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

  除此之外,利用此方法还可以实现在线视频播放缓冲流播放的功能,具体的后面有机会的话会研究一下

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

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

  目前写的逻辑,只支持客户端传Range为start-这种格式的数据

  补充下http返回code:

  200 OK(一切正常)

  206 Partial Content(服务器已经成功处理了部分内容)

  416 Requested Range Not Satisfiable(对方(客户端)发来的Range 请求头不合理)

  

/**

 

   * 断点下载文件

   * @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();

   //getHeader这个方法取请求头,是忽略大小写的!

   if (request.getHeader("Range") != null) {

   //返回206

   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-length

   response.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), file.length()));

   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() {

   @Override

   public 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;

   //文件总大小

   double fileAllLength = length;

   if (startSize 0) {

   //如果是断点续传,响应头的Content-Length不是文件的总长度

   fileAllLength = startSize + length;

   FileChannel foschannel;

   if (!TextUtils.isEmpty(resp.header("Content-Range"))) {

   //断点继续下载

   RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");

   //从上次未完成的位置开始下载

   randomAccessFile.seek(startSize);

   foschannel = randomAccessFile.getChannel();

   InputStream fis = resp.body().byteStream();

   ReadableByteChannel fisChannel = Channels.newChannel(fis);

   // 通道没有办法传输数据,必须依赖缓冲区

   // 分配指定大小的缓冲区

   ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

   // 将通道中的数据存入缓冲区中

   while (fisChannel.read(byteBuffer) != -1) { // fisChannel 中的数据读到 byteBuffer 缓冲区中

   byteBuffer.flip(); // 切换成读数据模式

   // 将缓冲区中的数据写入通道

   foschannel.write(byteBuffer);

   double progress = (foschannel.size() / fileAllLength);;

   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();

   } else {

   //走重新下载的逻辑

   FileOutputStream fileOutputStream = new FileOutputStream(file);

   foschannel = fileOutputStream.getChannel();

   InputStream fis = resp.body().byteStream();

   ReadableByteChannel fisChannel = Channels.newChannel(fis);

   // 通道没有办法传输数据,必须依赖缓冲区

   // 分配指定大小的缓冲区

   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();

   fileOutputStream.close();

   if (listener != null) {

   listener.onSuccess(file);

   } catch (IOException e) {

   if (listener != null) {

   listener.onError(e);

  
使用:

  

FileDownloadUtil.download(downloadUrl, file, new FileDownloadUtil.OnDownloadListener() {

 

   @Override

   public void onProgress(double progress) {

   KLog.d("下载进度: " + progress);

   //进度范围0-1

   @Override

   public void onError(Exception e) {

   KLog.e("下载错误: " + e.getMessage());

   @Override

   public void onSuccess(File outputFile) {

   KLog.d("下载成功");

  

 

  http请求中的range和Content-Range - 简书

  Http/1.1协议 Content-Range头 用于http断点续传_零点零一的博客-CSDN博客

  http视频文件传输(http 206)_怪力左手的博客-CSDN博客_206 http

  以上就是Java 断点下载(下载续传)服务端及客户端(Android)代码(java断点续传框架)的详细内容,想要了解更多 Java 断点下载(下载续传)服务端及客户端(Android)代码的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: