如何在Android中使用OkHttp实现下载的暂停和恢复

14

我在Android中使用okhttp库下载文件。我已成功下载,但是当我暂停并恢复下载时出现了一些问题。

Response request = new Request.Builder().url(url).build();
ResponseBody responseBody = response.body();

File file = new File(filePath);
BufferedInputStream input = new BufferedInputStream(responseBody.byteStream());
OutputStream output;

if (isResume) {
    output = new FileOutputStream(file, true);
    input.skip(downloadedSize);
} else {
    output = new FileOutputStream(file, false);    
}

long totalByteSize = responseBody.contentLength();
byte[] data = new byte[1024];
int count = 0;

while ((count = input.read(data)) != -1) {
    downloadedSize += count;
    output.write(data, 0, count);   
}

问题在于例如文件大小为10MB。 当下载了3MB时我暂停下载,然后继续下载,当下载完成时文件大小变为13MB。 它不是从已下载的大小开始恢复下载,而是从字节流的开头开始下载。 所以文件变成13MB。 代码哪里出错了?


1
你正在创建一个新的请求并发送它。这会导致响应保存整个文件。我不确定okHttp是否具有下载部分响应的能力,但如果有的话,这不是正确的方法。你至少需要传递一些关于从何处开始下载的数据。 - Gabe Sechan
我认为你的方法没有问题,可能是因为你的下载大小为0,所以没有跳过任何内容。我真的没有发现第一种方式中问题和答案之间有任何区别。如果有任何具体的区别,请告诉我。 - Shubham AgaRwal
@Killer differences是source.skip(downloadedSize)。你看不到的代码中,我从数据库获取文件下载状态和已下载大小。例如,文件大小为5.5MB,过去的下载过程中我已经下载了3MB。下一次下载过程必须调用source.skip(3MB转字节)。 - Alexander
3个回答

24

第一种方式

我尝试了很多代码,最终使用了 BufferedSource source = responseBody.source(); source.skip(downloadedSize); 解决了问题。

Response request = new Request.Builder().url(url).build();
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();

if(isResume)
    source.skip(downloadedSize);

File file = new File(filePath);
BufferedInputStream input = new BufferedInputStream(responseBody.byteStream());
OutputStream output;

if (isResume) {
    output = new FileOutputStream(file, true);
} else {
    output = new FileOutputStream(file, false);    
}

long currentDownloadedSize = 0;
long currentTotalByteSize = responseBody.contentLength();
byte[] data = new byte[1024];
int count = 0;
while ((count = input.read(data)) != -1) {
    currentDownloadedSize += count;
    output.write(data, 0, count);   
}

它成功地运行了。我想我很幸运 :)

第二种方法

我添加了头部跳过已下载的字节,它成功了。

Request.Builder requestBuilder = new Request.Builder();
if (isResume) {
    requestBuilder.addHeader("Range", "bytes=" + String.valueOf(downloadedSize) + "-");
}
Response request = requestBuilder.url(url).build();
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();

File file = new File(filePath);
BufferedInputStream input = new BufferedInputStream(responseBody.byteStream());
OutputStream output;

if (isResume) {
    output = new FileOutputStream(file, true);
} else {
    output = new FileOutputStream(file, false);    
}

long currentDownloadedSize = 0;
long currentTotalByteSize = responseBody.contentLength();
byte[] data = new byte[1024];
int count = 0;
while ((count = input.read(data)) != -1) {
    currentDownloadedSize += count;
    output.write(data, 0, count);   
}

如何在第一时间暂停下载?您能分享一些关于如何处理按钮点击时恢复部分的想法吗? - dazed'n'confused
@dazed'n'confused 抱歉,我无法理解你的问题。你在恢复/暂停方面遇到了什么问题? - Alexander
我正在尝试使用服务下载文件,我需要在服务中暂停下载,我该如何做? - dazed'n'confused
@dazed'n'confused,当文件暂停下载时,您需要保存最后下载的大小。在恢复下载时,您将使用已下载的大小来从哪个字节开始下载。实际上,您的问题的答案就在我的回答中https://dev59.com/k1cQ5IYBdhLWcg3wFP2a#43356615。 - Alexander
1
第二种方式更好,因为在第一种方式中,当您使用跳过时,客户端必须先下载文件,然后从第一个字节跳过N个字节。例如,您开始下载100 MB的文件并在50 MB处恢复下载,当您再次开始下载时,客户端会从头开始下载但会跳过50 MB,之后您才能读取输出。 - Golil

1
val call = client.newCall(request)

call.enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        listener.onFail(e)
    }

    override fun onResponse(call: Call, response: Response) {
        // write the stream to a file (downloading happening)
        val stream = response.body?.byteStream()
    }
})

// this will stop the downloading
call.cancel()

要恢复使用,请在您的请求中使用“Range”标头,并将流附加到已下载的文件中。


0
根据Alexander的回答,我尝试了他推荐的两种方法。第一种方法在下载停止后重新启动应用程序时会产生错误。我发现第二种方法更稳定。此外,代码中还存在语法错误。以下是一个示例代码。
        File file = new File(path);
        long availableFileLength = file.exists() && file.isFile() ? file.length() :0;
        Request.Builder requestBuilder = new Request.Builder();
        requestBuilder.addHeader("Range", "bytes=" + String.valueOf(availableFileLength) + "-");
        Request request = requestBuilder.url(url).build();
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .readTimeout(30, TimeUnit.SECONDS)
            .connectTimeout(30, TimeUnit.SECONDS)
            .build();
        ResponseBody responseBody = okHttpClient.newCall(request).execute().body();

        InputStream input = new BufferedInputStream(responseBody.byteStream());
        OutputStream output = new FileOutputStream(downloadPath, true);

        long currentDownloadedSize = 0;
        long currentTotalByteSize = responseBody.contentLength();
        byte[] data = new byte[1024];
        int count = 0;
        while ((count = input.read(data)) != -1) {
             currentDownloadedSize += count;
             output.write(data, 0, count);   
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接