1. 概述

本教程将提供如何 使用OkHttp 库下载二进制文件 的实际示例。

2.Maven依赖

我们首先添加基础库okhttp依赖项:

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

那么, 如果我们想为OkHttp库实现的模块编写集成测试,我们可以使用mockwebserver 。该库具有模拟服务器及其响应的工具:

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

3. 请求二进制文件

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

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

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

接下来,我们将实现 从 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);
}

下载文件的过程有四个步骤。使用 URL 创建请求。执行请求并接收响应。获取响应的正文,如果为 空则失败。 将响应正文的 字节 写入文件。

4. 将响应写入本地文件

要将从响应中接收到的 字节 写入本地文件,我们将实现一个 BinaryFileWriter 类,该类 InputStreamOutputStream 作为输入,并将内容从 InputStream 复制到 OutputStream

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

public class BinaryFileWriter implements AutoCloseable {

    private final OutputStream outputStream;

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

我们现在将实现将内容从 InputStream 复制到 OutputStream 的方法。该方法首先用 BufferedInputStream 包装 InputStream ,以便我们可以一次读取更多 字节 。然后我们准备一个数据缓冲区,在其中临时存储来自 InputStream 的 字节

最后,我们将缓冲的数据 写入 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. 获取文件下载进度

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

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

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

然后,我们将在 BinaryFileWriter 类中使用它。这将为我们提供每一步下载器迄今为止写入的总 字节数

首先,我们将 ProgressCallback 作为字段添加到 writer 类中 。然后,我们将更新 write 方法以 接收响应的 长度 作为参数 。这将帮助我们计算进度。

然后,我们将 调用 onProgress 方法,并 根据迄今为止写入的 总字节数长度计算出进度:

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

最后,我们将更新 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")));
    }
}

六,结论

在本文中,我们实现了一个简单但实用的示例, 使用 OkHttp 库从 URL 下载二进制文件

要完整实现文件下载应用程序以及单元测试,请在 GitHub 上查看该项目。