Bootstrap

okhttp断点续传

使用 OkHttp 实现断点续传,关键是通过设置 Range 请求头向服务器请求文件的部分内容。下面是详细的实现步骤和示例代码。

步骤

  1. 获取文件的总大小:在开始下载前,通常需要知道文件的总大小,可以通过发送一个初始请求来获取。
  2. 记录已下载字节数:记录已下载的字节数,确保在下载中断后能够继续。
  3. 分块下载:每次发送一个 Range 请求,告诉服务器从哪个字节位置开始传输文件,并获取相应的字节块。
  4. 处理中断续传:如果下载过程中中断,可以通过记录的已下载字节数,从上次停止的位置继续下载。

代码实现

1. 添加 OkHttp 库

build.gradle 中添加 OkHttp 的依赖:

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.9.0")
}
2. 定义下载函数

使用 Range 请求头来获取文件的部分内容。

import okhttp3.*;
import java.io.*;
import java.util.concurrent.TimeUnit;

public class FileDownloader {
    private static final String FILE_URL = "http://example.com/largefile.zip";  // 文件 URL
    private static final String FILE_PATH = "your/local/path/largefile.zip";    // 保存路径
    private static final long CHUNK_SIZE = 1024 * 1024;  // 每次下载 1MB

    private OkHttpClient client;
    private long downloadedBytes = 0;  // 已下载字节数

    public FileDownloader() {
        client = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();
    }

    // 获取已下载的字节数
    private long getDownloadedBytes() {
        File file = new File(FILE_PATH);
        if (file.exists()) {
            return file.length();
        }
        return 0;
    }

    // 保存下载的数据到本地文件
    private void saveToFile(byte[] data) {
        try (FileOutputStream fos = new FileOutputStream(FILE_PATH, true)) {  // 以追加模式写入文件
            fos.write(data);
            fos.flush();
            downloadedBytes += data.length;  // 更新已下载字节数
            System.out.println("Downloaded " + downloadedBytes + " bytes.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 下载文件块
    private void downloadFileChunk(long startByte, long endByte) {
        Request request = new Request.Builder()
                .url(FILE_URL)
                .header("Range", "bytes=" + startByte + "-" + endByte)  // 设定下载范围
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
                System.out.println("Download failed");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    byte[] data = response.body().bytes();
                    saveToFile(data);  // 保存文件
                    if (downloadedBytes < response.body().contentLength()) {
                        // 继续下载下一个部分
                        long nextStartByte = downloadedBytes;
                        long nextEndByte = Math.min(nextStartByte + CHUNK_SIZE - 1, response.body().contentLength());
                        downloadFileChunk(nextStartByte, nextEndByte);  // 递归下载下一个部分
                    } else {
                        System.out.println("Download completed");
                    }
                } else {
                    System.out.println("Server response failed: " + response.code());
                }
            }
        });
    }

    // 启动下载任务
    public void startDownload() {
        downloadedBytes = getDownloadedBytes();  // 获取当前已下载的字节数
        long totalFileSize = getFileSize();  // 获取文件的总大小
        long startByte = downloadedBytes;
        long endByte = Math.min(startByte + CHUNK_SIZE - 1, totalFileSize);

        downloadFileChunk(startByte, endByte);  // 开始下载文件
    }

    // 获取文件总大小(可以通过 HEAD 请求或事先知道文件的大小)
    private long getFileSize() {
        Request request = new Request.Builder()
                .url(FILE_URL)
                .head()  // HEAD 请求
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                return response.body().contentLength();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return 0;
    }
}

解释

  1. 获取已下载的字节数

    • 通过 getDownloadedBytes() 方法来检查本地文件是否已经部分下载。如果文件已存在,返回已下载的字节数。
  2. 分块下载

    • 每次下载 1MB(CHUNK_SIZE 设置为 1MB),通过 Range 请求头向服务器请求文件的指定字节范围(例如 Range: bytes=0-1048575 请求从 0 到 1MB 的文件)。
  3. 保存文件并更新进度

    • 下载的文件块会通过 saveToFile() 方法保存到本地。如果下载的文件还没有完成,则继续请求下一个块。下载完成时,会打印 Download completed
  4. 获取文件总大小

    • 使用 HEAD 请求获取文件的大小,确保可以计算分块的范围。如果你已知文件的总大小,也可以省略这一步。
  5. 递归下载

    • 下载完成一个块后,递归调用 downloadFileChunk() 方法下载下一个块,直到文件完全下载。

;