在 OkHttp 中實現斷點續傳主要通過以下步驟完成,核心是利用 HTTP 協議的?Range
?請求頭指定下載范圍:
實現原理
-
Range 請求頭:向服務器請求文件的特定字節范圍(如?
Range: bytes=1024-
) -
本地文件記錄:保存已下載的字節位置
-
206 狀態碼處理:服務器返回部分內容(HTTP 206 Partial Content)
-
文件追加寫入:從上次中斷的位置繼續寫入文件
完整實現代碼(Kotlin/Java)
kotlin
import okhttp3.*
import java.io.File
import java.io.IOException
import java.io.RandomAccessFileclass 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 Contentval totalBytes = response.header("Content-Length")?.toLongOrNull() ?: -1Lval 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: Intvar 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
// 創建帶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 Contenttry (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);}}}
}
關鍵注意事項
-
服務器支持檢查:
-
成功時返回?HTTP 206(部分內容)
-
失敗時返回 200(完整文件)或 416(范圍請求錯誤)
-
響應頭需包含?
Accept-Ranges: bytes
-
-
文件處理:
-
使用?
RandomAccessFile
?實現文件隨機訪問 -
通過?
seek()
?定位到文件末尾 -
避免覆蓋已下載內容
-
-
進度跟蹤:
-
總大小 = 已下載大小 +?
Content-Length
-
實時計算:
currentBytes += bytesRead
-
-
異常處理:
-
網絡中斷時保存當前進度
-
重新下載時使用最新文件長度
-
增強功能建議
-
進度持久化:使用數據庫記錄下載狀態
-
暫停/恢復:暴露下載控制接口
-
多線程下載:分割文件范圍并行下載(需服務器支持)
-
完整性校驗:下載完成后驗證文件 MD5/SHA1
示例用法:
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 可在網絡中斷后自動從斷點恢復下載,大幅提升大文件下載體驗。