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

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

一、要注意的地方

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

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) {
                    }
                }
            }
        });
    }
发布了61 篇原创文章 · 获赞 8 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/fly_yuge/article/details/105737805
今日推荐