使用 OkHttp 实现断点续传,关键是通过设置 Range
请求头向服务器请求文件的部分内容。下面是详细的实现步骤和示例代码。
步骤
- 获取文件的总大小:在开始下载前,通常需要知道文件的总大小,可以通过发送一个初始请求来获取。
- 记录已下载字节数:记录已下载的字节数,确保在下载中断后能够继续。
- 分块下载:每次发送一个
Range
请求,告诉服务器从哪个字节位置开始传输文件,并获取相应的字节块。 - 处理中断续传:如果下载过程中中断,可以通过记录的已下载字节数,从上次停止的位置继续下载。
代码实现
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;
}
}
解释
-
获取已下载的字节数:
- 通过
getDownloadedBytes()
方法来检查本地文件是否已经部分下载。如果文件已存在,返回已下载的字节数。
- 通过
-
分块下载:
- 每次下载 1MB(
CHUNK_SIZE
设置为 1MB),通过Range
请求头向服务器请求文件的指定字节范围(例如Range: bytes=0-1048575
请求从 0 到 1MB 的文件)。
- 每次下载 1MB(
-
保存文件并更新进度:
- 下载的文件块会通过
saveToFile()
方法保存到本地。如果下载的文件还没有完成,则继续请求下一个块。下载完成时,会打印Download completed
。
- 下载的文件块会通过
-
获取文件总大小:
- 使用
HEAD
请求获取文件的大小,确保可以计算分块的范围。如果你已知文件的总大小,也可以省略这一步。
- 使用
-
递归下载:
- 下载完成一个块后,递归调用
downloadFileChunk()
方法下载下一个块,直到文件完全下载。
- 下载完成一个块后,递归调用