文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android(Java)下载断点续传的实现

2022-06-06 14:08

关注

Android(Java)下载断点续传的实现一、要注意的地方1. 追加文件2. 跳过输入流3.range header坑点1.坑点2坑点3.二、代码方法1方法2

最近在做一个下载文件的功能的时候,因为要支持断点续传,虽然整体上思路很清晰,也比较简单,但是在做的过程中还是遇到了几个坑,特意在此记录一下,也重新梳理下下载断点续传的实现过程。

一、要注意的地方

这里先把几个坑点先说明一下:

1. 追加文件

如果你写入文件用的是FileOutputStream,那么一定要用两参的构造方法:

```java
new FileOutputStream(file,true); // 第二个参数为true
```
2. 跳过输入流

当你用方法1(通过inputStream的skip)去实现断点续传的时候,skip方法有坑,简单来说就是skip方法不能保证一定会跳过你指定的字节数,所以你自己要做一个简单处理:

private void skip(InputStream in, long offset) throws IOException {
        long skip = 0;
        do {
            offset -= skip;
            skip = in.skip(offset);
            if (skip <= 0) {
                break;
            }
        } while (skip != offset);
    }
3.range header

当你用方法2(http range header)去实现断点续传的时候,有如下三个坑:

坑点1.

首先要确保服务器支持断点续传,我当时就是因为从网上先随便找个文件来下载,一些文件的服务器是不支持的,导致我调试了好久好久。。。那么怎么判断服务器是否支持呢? 当然是问服务器的同学啦,如果他们也不确定的话(又一个坑~),求人不如求己,可以通过如下命令查看:

curl -i --range 100-200 http://app.znds.com/down/20200331/w2zry_1.53.1.6_dangbei.apk

看它返回的数据

HTTP/1.1 206 Partial Content
Server: JSP3/2.0.14
Date: Fri, 24 Apr 2020 04:21:54 GMT
Content-Type: application/vnd.android.package-archive
Content-Length: 101
Connection: keep-alive
ETag: "7629026C24EBCA9B219A6BEC4DA7E475"
Last-Modified: Tue, 31 Mar 2020 08:02:33 GMT
Age: 863675
Content-Range: bytes 100-200/393647401
Accept-Ranges: bytes
x-oss-request-id: 5E82FA10146F3833363BA5BA
x-oss-object-type: Normal
x-oss-hash-crc64ecma: 14417895789503488492
x-oss-storage-class: Standard
Content-MD5: dikCbCTrypshmmvsTafkdQ==
x-oss-server-time: 9
Ohc-File-Size: 393647401
Timing-Allow-Origin: *
Ohc-Cache-HIT: cangzuncache63 [4], qdix131 [3]

如果也Content-Range 和 Accept-Ranges,那说明支持,否则就别费劲了,用方法1吧。

坑点2

第二个坑点是,给request设置header的时候,代码如下:

header("Range", "bytes=" + offset + "-")

一定不能缺少最后的那个"-",否则这个就是个无效的header。

坑点3.

第三个坑点是,当你给你的request添加了这个header之后,你要计算下载百分比吧,我之前计算百分比的分母(即文件大小)的时候,用的是Content-length里返回的数据,那么问题就来了,当你断点续传的时候,返回的数据流里不是所有数据的大小,所以content-length肯定就变小了,这时你在计算百分比肯定就错误了(好吧,这是我自己的问题),解决办法有两个,第一个还是每次都取content-length,但是不能直接用来做分母,要和本地文件的大小做一个加和;第二个就是服务器直接在反水的数据里告诉你文件大小,不取content-length。

二、代码

这里我还用了OKhttp做网络请求。
下面两种方法我都是自己传进来了文件的大小,如果不想自己传的话,下面的size可以改成,contentLength自己去获取吧。

long size = contentLength + offset;
方法1

优点:不依赖服务器的支持,完全端上自己实现;
缺点:会浪费流量;

public void download(final String url, final String path, long offset,
                         long size, final OnDownloadListener listener) {
        Request request =
                new Request.Builder()
                        .url(url)
                        .build();
        downloadCall = okHttpClient.newCall(request);
        downloadCall.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                // 下载失败
                listener.onDownloadFailed(1);
            }
            @Override
            public void onResponse(Call call, Response response) {
                listener.onDownloadStart();
                InputStream is = null;
                byte[] buf = new byte[2048];
                int len = 0;
                FileOutputStream fos = null;
                try {
                    is = response.body().byteStream();
                    // 跳过offset文件大小
                    skip(is,offset);
                    File file = new File(path);
                    fos = new FileOutputStream(file, true);
                    long sum = offset;
                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                        sum += len;
                        Log.d("downloadtUtil", "download length:" + sum);
                        int progress = (int) (sum * 1.0f / size * 100);
                        // 下载中
                        listener.onDownloading(progress);
                    }
                    fos.flush();
                    // 下载完成
                    listener.onDownloadSuccess();
                } catch (SocketException e) {
                    e.printStackTrace();
                    listener.onDownloadFailed(1);
                } catch (IOException e) {
                    e.printStackTrace();
                    listener.onDownloadFailed(0);
                } finally {
                    try {
                        if (is != null) {
                            is.close();
                        }
                    } catch (IOException e) {
                    }
                    try {
                        if (fos != null) {
                            fos.close();
                        }
                    } catch (IOException e) {
                    }
                }
            }
        });
    }
方法2

优点:节省流量,已经下载过的数据不再下载了,真正的断点续传;
缺点:依赖服务器配置或者服务器开发人员开发;

public void download(final String url, final String path, long offset,
                         long size, final OnDownloadListener listener) {
        Request request =
                new Request.Builder()
                        .header("Range", "bytes=" + offset + "-")
                        .url(url)
                        .build();
        downloadCall = okHttpClient.newCall(request);
        downloadCall.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                // 下载失败
                listener.onDownloadFailed(1);
            }
            @Override
            public void onResponse(Call call, Response response) {
                HLog.d("http request:" + response.request().headers());
                HLog.d("http request:" + response.headers());
                listener.onDownloadStart();
                InputStream is = null;
                byte[] buf = new byte[2048];
                int len = 0;
                FileOutputStream fos = null;
                // 储存下载文件的目录
                try {
                    is = response.body().byteStream();
                    File file = new File(path);
                    fos = new FileOutputStream(file, true);
                    long sum = offset;
                    while ((len = is.read(buf)) != -1) {
                        fos.write(buf, 0, len);
                        sum += len;
                        Log.d("downloadtUtil", "download length:" + sum);
                        int progress = (int) (sum * 1.0f / size * 100);
                        // 下载中
                        listener.onDownloading(progress);
                    }
                    fos.flush();
                    // 下载完成
                    listener.onDownloadSuccess();
                } catch (SocketException e) {
                    e.printStackTrace();
                    listener.onDownloadFailed(1);
                } catch (IOException e) {
                    e.printStackTrace();
                    listener.onDownloadFailed(0);
                } finally {
                    try {
                        if (is != null) {
                            is.close();
                        }
                    } catch (IOException e) {
                    }
                    try {
                        if (fos != null) {
                            fos.close();
                        }
                    } catch (IOException e) {
                    }
                }
            }
        });
    }

作者:宇光十色_FLY


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯