Download a Binary File Using OkHttp – 使用OkHttp下载二进制文件

最后修改: 2021年 6月 17日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

This tutorial will give a practical example of how to download a binary file using the OkHttp library.

本教程将给出一个实际例子,说明如何使用OkHttp库下载一个二进制文件。

2. Maven Dependencies

2.Maven的依赖性

We’ll start by adding the base library okhttp dependency:

我们将首先添加基础库okhttp依赖。

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>

Then, if we want to write an integration test for the module implemented with the OkHttp library, we can use the mockwebserver library. This library has the tools to mock a server and its responses:

然后,如果我们想为用OkHttp库实现的模块写一个集成测试,我们可以使用mockwebserver。这个库拥有模拟服务器及其响应的工具。

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>mockwebserver</artifactId>
    <version>4.9.1</version>
    <scope>test</scope>
</dependency>

3. Requesting a Binary File

3.要求提供二进制文件

We’ll first implement a class that receives as a parameter a URL from where to download the file and creates and executes an HTTP request for that URL.

我们首先要实现一个类,接收一个URL作为参数,从那里下载文件,为该URL创建并执行一个HTTP请求

To make the class testable, we’ll inject the OkHttpClient and the writer in the constructor:

为了使该类可测试,我们将在构造函数中注入OkHttpClient和写程序

public class BinaryFileDownloader implements AutoCloseable {

    private final OkHttpClient client;
    private final BinaryFileWriter writer;

    public BinaryFileDownloader(OkHttpClient client, BinaryFileWriter writer) {
        this.client = client;
        this.writer = writer;
    }
}

Next, we’ll implement the method that downloads the file from the URL:

接下来,我们将实现从URL下载文件的方法

public long download(String url) throws IOException {
    Request request = new Request.Builder().url(url).build();
    Response response = client.newCall(request).execute();
    ResponseBody responseBody = response.body();
    if (responseBody == null) {
        throw new IllegalStateException("Response doesn't contain a file");
    }
    double length = Double.parseDouble(Objects.requireNonNull(response.header(CONTENT_LENGTH, "1")));
    return writer.write(responseBody.byteStream(), length);
}

The process of downloading the file has four steps. Create the request using the URL. Execute the request and receive a response. Get the body of the response, or fail if it’s null. Write the bytes of the body of the response to a file.

下载文件的过程有四个步骤。使用URL创建请求。执行请求,并收到一个响应。获取响应的主体,如果它是null,则失败。将响应主体的bytes写到一个文件中。

4. Writing the Response to a Local File

4.将响应写到本地文件中

To write the received bytes from the response to a local file, we’ll implement a BinaryFileWriter class which takes as an input an InputStream and an OutputStream and copies the contents from the InputStream to the OutputStream.

为了把收到的bytes从响应中写到一个本地文件。我们将实现一个BinaryFileWriter类,接收一个InputStream和一个OutputStream作为输入,并将InputStream中的内容复制到OutputStream

The OutputStream will be injected into the constructor so that the class can be testable:

OutputStream将被注入构造函数中,以便该类可以被测试。

public class BinaryFileWriter implements AutoCloseable {

    private final OutputStream outputStream;

    public BinaryFileWriter(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
}

We’ll now implement the method that copies the contents from the InputStream to the OutputStream. The method first wraps the InputStream with a BufferedInputStream so that we can read more bytes at once. Then we prepare a data buffer in which we temporarily store the bytes from the InputStream.

现在我们将实现将内容从InputStream复制到OutputStream的方法。这个方法首先用一个BufferedInputStream来包装InputStream,这样我们就可以一次性读取更多的bytes。然后我们准备一个数据缓冲区,将bytesInputStream中临时存储起来。

Finally, we’ll write the buffered data to the OutputStream. We do this as long as the InputStream has data to be read:

最后,我们将缓冲的数据到OutputStream。只要InputStream有数据要读,我们就这么做。

public long write(InputStream inputStream) throws IOException {
    try (BufferedInputStream input = new BufferedInputStream(inputStream)) {
        byte[] dataBuffer = new byte[CHUNK_SIZE];
        int readBytes;
        long totalBytes = 0;
        while ((readBytes = input.read(dataBuffer)) != -1) {
            totalBytes += readBytes;
            outputStream.write(dataBuffer, 0, readBytes);
        }
        return totalBytes;
    }
}

5. Getting the File Download Progress

5.获取文件下载进度

In some cases, we might want to tell the user the progress of a file download.

在某些情况下,我们可能想告诉用户一个文件下载的进度。

We’ll first need to create a functional interface:

我们首先需要创建一个功能接口

public interface ProgressCallback {
    void onProgress(double progress);
}

Then, we’ll use it in the BinaryFileWriter class. This will give us at every step the total bytes that the downloader wrote so far.

然后,我们将在BinaryFileWriter类中使用它。这将在每一步给我们提供下载器到目前为止所写的总字节

First, we’ll add the ProgressCallback as a field to the writer class. Then, we’ll update the write method to receive as a parameter the length of the response. This will help us calculate the progress.

首先,我们将添加ProgressCallback作为写入器类的一个字段。然后,我们将更新write方法,接收响应的length作为参数。这将帮助我们计算出进度。

Then, we’ll call the onProgress method with the calculated progress from the totalBytes written so far and the length:

然后,我们将调用onProgress方法,计算出的进度来自到目前为止写入的totalByteslength:

public class BinaryFileWriter implements AutoCloseable {
    private final ProgressCallback progressCallback;
    public long write(InputStream inputStream, double length) {
        //...
        progressCallback.onProgress(totalBytes / length * 100.0);
    }
}

Finally, we’ll update the BinaryFileDownloader class to call the write method with the total response length. We’ll get the response length from the Content-Length header, then pass it to the write method:

最后,我们将更新BinaryFileDownloader类,以便用总的响应长度调用write方法。我们将从Content-Length头中获取响应长度,然后将其传递给write方法。

public class BinaryFileDownloader {
    public long download(String url) {
        double length = getResponseLength(response);
        return write(responseBody, length);
    }
    private double getResponseLength(Response response) {
        return Double.parseDouble(Objects.requireNonNull(response.header(CONTENT_LENGTH, "1")));
    }
}

6. Conclusion

6.结论

In this article, we implemented a simple yet practical example of downloading a binary file from a URL using the OkHttp library.

在这篇文章中,我们实现了一个简单而实用的例子:使用OkHttp库从URL中下载一个二进制文件

For a full implementation of the file download application, along with the unit tests, check out the project over on GitHub.

有关文件下载应用程序的完整实现,以及单元测试,请查看GitHub上的项目