OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围:

实现原理

  1. Range 请求头 :向服务器请求文件的特定字节范围(如 Range: bytes=1024-)

  2. 本地文件记录:保存已下载的字节位置

  3. 206 状态码处理:服务器返回部分内容(HTTP 206 Partial Content)

  4. 文件追加写入:从上次中断的位置继续写入文件

完整实现代码(Kotlin/Java)

kotlin

Kotlin 复制代码
import okhttp3.*
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile

class ResumeDownloader(
    private val client: OkHttpClient = OkHttpClient()
) {
    fun download(url: String, file: File, listener: ProgressListener? = null) {
        // 1. 获取已下载字节数
        val downloadedBytes = if (file.exists()) file.length() else 0L

        // 2. 创建带Range头的请求
        val request = Request.Builder()
            .url(url)
            .header("Range", "bytes=$downloadedBytes-")
            .build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                listener?.onError(e)
            }

            override fun onResponse(call: Call, response: Response) {
                if (!response.isSuccessful) {
                    listener?.onError(IOException("Unexpected code: ${response.code}"))
                    return
                }

                // 3. 检查服务器是否支持断点续传
                val isResumeSupported = response.code == 206 // Partial Content
                val totalBytes = response.header("Content-Length")?.toLongOrNull() ?: -1L
                val finalTotalBytes = if (isResumeSupported) {
                    downloadedBytes + (totalBytes)
                } else {
                    totalBytes
                }

                // 4. 处理响应体
                response.body?.use { body ->
                    RandomAccessFile(file, "rw").use { output ->
                        // 移动到文件末尾追加
                        output.seek(downloadedBytes)

                        val input = body.byteStream()
                        val buffer = ByteArray(8192)
                        var bytesRead: Int
                        var progress = downloadedBytes

                        // 5. 写入文件
                        while (input.read(buffer).also { bytesRead = it } != -1) {
                            output.write(buffer, 0, bytesRead)
                            progress += bytesRead
                            // 更新进度
                            listener?.onProgress(progress, finalTotalBytes)
                        }
                        listener?.onComplete(file)
                    }
                }
            }
        })
    }

    interface ProgressListener {
        fun onProgress(currentBytes: Long, totalBytes: Long)
        fun onComplete(file: File)
        fun onError(e: Exception)
    }
}

Java 版本核心代码

java

java 复制代码
// 创建带Range头的请求
long downloadedBytes = file.length();
Request request = new Request.Builder()
    .url(url)
    .addHeader("Range", "bytes=" + downloadedBytes + "-")
    .build();

// 处理响应
try (Response response = client.newCall(request).execute()) {
    if (response.code() == 206) { // Partial Content
        try (RandomAccessFile output = new RandomAccessFile(file, "rw");
             InputStream input = response.body().byteStream()) {
            
            output.seek(downloadedBytes);
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = input.read(buffer)) != -1) {
                output.write(buffer, 0, bytesRead);
            }
        }
    }
}

关键注意事项

  1. 服务器支持检查:

    • 成功时返回 HTTP 206(部分内容)

    • 失败时返回 200(完整文件)或 416(范围请求错误)

    • 响应头需包含 Accept-Ranges: bytes

  2. 文件处理:

    • 使用 RandomAccessFile 实现文件随机访问

    • 通过 seek() 定位到文件末尾

    • 避免覆盖已下载内容

  3. 进度跟踪:

    • 总大小 = 已下载大小 + Content-Length

    • 实时计算:currentBytes += bytesRead

  4. 异常处理:

    • 网络中断时保存当前进度

    • 重新下载时使用最新文件长度

增强功能建议

  1. 进度持久化:使用数据库记录下载状态

  2. 暂停/恢复:暴露下载控制接口

  3. 多线程下载:分割文件范围并行下载(需服务器支持)

  4. 完整性校验:下载完成后验证文件 MD5/SHA1

示例用法:

kotlin

Kotlin 复制代码
val downloader = ResumeDownloader()
val file = File(context.filesDir, "largefile.zip")

downloader.download("https://example.com/largefile.zip", file,
    object : ResumeDownloader.ProgressListener {
        override fun onProgress(current: Long, total: Long) {
            val percent = (current * 100 / total).toInt()
            updateProgressBar(percent)
        }
        
        override fun onComplete(file: File) {
            showToast("下载完成")
        }
        
        override fun onError(e: Exception) {
            showError(e.message)
        }
    }
)

通过此实现,OkHttp 可在网络中断后自动从断点恢复下载,大幅提升大文件下载体验。

相关推荐
没差c4 天前
RetrofitClient写接口、导包、配置、路径
java·spring boot·okhttp
事圆则缓14 天前
OkHttp + Retrofit 协作原理
okhttp·retrofit
降临-max17 天前
接口自动化(TestNG+OKHttp)
软件测试·okhttp·自动化·testng
吴声子夜歌17 天前
Java扩展——OkHttp
java·开发语言·okhttp
额鹅恶饿呃18 天前
Asynchronous JavaScript And XML(异步的JS和XML )
okhttp
泡海椒18 天前
JQuick-Curl 性能分析:并发场景下的性能表现与调优,第三方接口调用不只要快写也要稳跑
java·开发语言·okhttp
泡海椒18 天前
JQuick-Curl 二次开发:自定义解析器、扩展点开发指南,如何把框架能力真正变成团队资产
java·开发语言·okhttp·maven
摇滚侠19 天前
《SpringBoot 3:入门与应用实战》第 9 章 跨域问题 阅读笔记 27
spring boot·笔记·okhttp
刘广睿21 天前
断点续传与分片下载是怎么实现的?Range 请求在视频下载中的工程实践
okhttp·ffmpeg·音视频·下载·python3.11
泡海椒1 个月前
JQuick-Curl vs RestTemplate / OkHttp / OpenFeign:框架选型对比,谁更适合第三方接口调用?
okhttp