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 可在网络中断后自动从断点恢复下载,大幅提升大文件下载体验。

相关推荐
洛克大航海3 小时前
Ajax基本使用
java·javascript·ajax·okhttp
whltaoin6 天前
Java 网络请求 Jar 包选型指南:从基础到实战
java·http·okhttp·网络请求·retrofit
华农第一蒟蒻7 天前
谈谈跨域问题
java·后端·nginx·安全·okhttp·c5全栈
一直向钱9 天前
android 基于okhttp的socket封装
android·okhttp
linuxxx1109 天前
ajax回调钩子的使用简介
okhttp
一直向钱10 天前
android 基于okhttp 封装一个websocket管理模块,方便开发和使用
android·websocket·okhttp
linuxxx11011 天前
ajax() 回调函数参数详解
前端·ajax·okhttp
linuxxx11013 天前
ajax与jQuery是什么关系?
ajax·okhttp·jquery
耀耀_很无聊15 天前
12_OkHttp初体验
okhttp
heeheeai15 天前
okhttp使用指南
okhttp·kotlin·教程