当前位置: 首页 > news >正文

Android 档案下载实践:基于 OkHttp 的完整实现与思考

一. 引言

文件下载几乎是每一个稍微复杂一点的 App 都绕不开的需求:下载资源包、音视频文件、离线数据、更新包……可以说是“老朋友”了。

在日常开发中,我们通常都会使用一套成熟的网络请求方案,比如:

  • OkHttpClient:底层网络通信
  • Retrofit:接口层封装
  • Moshi / Gson:JSON 序列化与反序列化

这些框架对接口请求的支持已经非常完善了,写起来也非常优雅。

但是当我想下载数据时比如zip,或者是.mp3,.mp4,当前接口请求这一套就有点满足不了了。

二. 网络请求方案简单回顾

在谈到下载之前,我们先简单回顾一下这网络三套各自的职责。

OkHttpClient

  • 底层 HTTP 客户端
  • 负责:连接、请求、响应、流读取
  • 非常灵活,但偏底层

下载本质上就是 OkHttp 最擅长、但又最原始的事情

Retrofit

  • 基于 OkHttp 的接口封装层
  • 用注解描述 API
  • 非常适合:请求 JSON → 转 Model

但对于下载来说:

  • Retrofit 仍然是 流式下载
  • 进度监听、文件写入,最终还是要你自己处理

Moshi

  • JSON ↔ Model 转换
  • 与下载本身关系不大

那么这么分析完之后,我们就可以看出下载这一套流程和网络请求需要分开来写了。

我们都希望 OkHttp 可以提供一个简单的下载方法,给我们回调进度,给我们下载后的资源地址,但是很遗憾并没有。

OkHttp对下载的态度是 :我只负责网络通讯,至于你要怎么处理数据,那是开发者的事儿。

所以在下载场景中,OkHttp 只给我们:

  • ResponseBody
  • 一个 InputStream

剩下的全交给开发者:

  • 下载进度怎么算
  • 文件存哪
  • 写文件的方式
  • 是否支持协程
  • 异常如何处理

三. 基于 OkHttp 的下载实现

 既然需要我们自己来实现细节,那么在写代码之前我们首先要明确,一个完整的下载,至少需要处理哪些事情。

  1. 发起网络请求
  2. 校验 HTTP 状态码
  3. 获取文件总大小(用于进度)
  4. 从 InputStream 读取数据
  5. 按 buffer 写入本地文件
  6. 计算并回调下载进度
  7. 处理异常
  8. 切换到 IO 线程执行

接下来,我们开始围绕上面的这些点开始一步步的展开。

3.1 代码实现

下面是一个简单但是很完整的下载工具类:DownloadHelper

package com.example.americandramaassistantandroid.network
import okhttp3.OkHttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Request
import java.io.File
import java.io.IOException
class DownloadHelper {private val client = OkHttpClient()/// 下载suspend fun downloadZip(url: String,targetFile: File,onProgress: (Int) -> Unit) = withContext(Dispatchers.IO) {val request = Request.Builder().url(url).build()client.newCall(request).execute().use { response ->if (!response.isSuccessful) {throw IOException("Download failed: ${response.code}")}val body = response.body?: throw IOException("Response body is null")val contentLength = body.contentLength()val inputStream = body.byteStream()var totalRead = 0Lval buffer = ByteArray(8 * 1024)targetFile.outputStream().use { output ->while (true) {val read = inputStream.read(buffer)if (read == -1) breakoutput.write(buffer, 0, read)totalRead += readif (contentLength > 0) {val progress = (totalRead * 100 / contentLength).toInt()onProgress(progress)}}output.flush()}}}
}

3.2 代码解析

使用协程切换到 IO 线程

withContext(Dispatchers.IO)
  • 下载和文件写入都属于 IO 操作
  • 避免阻塞主线程
  • 与 ViewModel 非常好配合

构建并执行请求

val request = Request.Builder().url(url).build()
client.newCall(request).execute()

  • 这里使用的是 同步请求
  • 但因为在 IO 线程中执行,不会卡 UI
  • 同步请求更利于控制下载流程

校验响应合法性

if (!response.isSuccessful) {throw IOException("Download failed: ${response.code}")
}

  • 下载失败往往不是“读不到流”
  • 而是 HTTP 状态码就已经失败了

获取文件大小 & 输入流

val contentLength = body.contentLength()
val inputStream = body.byteStream()

  • contentLength 用来计算进度
  • 某些服务器可能返回 -1,这也是为什么要做判断

边读边写文件

val buffer = ByteArray(8 * 1024)
while (true) {val read = inputStream.read(buffer)if (read == -1) breakoutput.write(buffer, 0, read)totalRead += read
}

这是下载的核心逻辑

  • 8KB buffer 是一个常见、合理的大小
  • 一边从网络读
  • 一边往文件写
  • 不占用大量内存

下载进度回调

val progress = (totalRead * 100 / contentLength).toInt()
onProgress(progress)

  • 进度完全由我们自己控制
  • 可以很容易和 UI 层绑定
  • 比 Retrofit 的下载监听更直观

四. 使用

在 ViewModel 中使用非常自然:

viewModelScope.launch {downloadHelper.downloadZip(url = downloadUrl,targetFile = targetFile) { progress ->_downloadProgress.value = progress}
}

我们只需要传入文件的下载地址,和文件的保存路径。

而UI层就只需要关心:当前进度、是否完成、是否失败。

五. 结语

下载看起来是一个“老生常谈”的功能,但真正自己实现一次,你会发现:

  • 它非常底层
  • 但也非常可控
  • 非常适合用来理解 OkHttp 的本质

OkHttp 并没有替你“把事做完”,而是把选择权全部交给了你。

这既是负担,也是自由。

不过目前就只是个最简单的下载器,如果进一步扩展的话,可以添加下载任务的取消与状态管理,或者支持断点续传的下载实现等等。

也欢迎大家一起讨论关于安卓中的下载。

http://www.jsqmd.com/news/377225/

相关文章:

  • AI应用开发怎么样?未来发展方向有哪些?
  • 京东E卡回收攻略,快速变现技巧! - 团团收购物卡回收
  • 2026年度云南旅行社推荐榜单:服务品质与游客体验双维度综合评估 - 品牌推荐
  • 2026年国内旅游必打卡路线十大热门榜单综合评估报告发布 - 品牌推荐
  • 2026年度权威发布:最新云南旅行社实力服务与品质体验深度解析 - 品牌推荐
  • AI怎么读懂文字?——国王减去男人等于什么
  • 2026年度美国投资移民机构TOP10综合评估与选型指南 - 品牌推荐
  • 2026年国内旅游必打卡路线十大热门规划:权威评测与经典推荐 - 品牌推荐
  • 2026年美国投资移民机构推荐:政策收紧下的专业选择与综合排名 - 品牌推荐
  • 7天速通Java!从小白入门到高薪就业!把JavaSE到微服务分布式一次性给大家讲明白!
  • AIVideo使用技巧:提升视频质量的5个秘诀
  • 均胜电子联合中际旭创推出车载光通信解决方案,已具备量产上车能力
  • WeKnora效果惊艳展示:对OCR识别错误的文本(如‘O’识别为‘0’),具备容错理解能力
  • GLM-4-9B-Chat-1M实操手册:金融投研场景——批量解析年报PDF并提取财务指标
  • 详细介绍:TDengine Go 语言连接器进阶指南
  • 2026年国内旅游必打卡路线推荐:十大经典规划排名,涵盖家庭亲子与自驾场景 - 品牌推荐
  • 深求·墨鉴实战:会议纪要整理效率提升300%
  • 2026年国内旅游必打卡路线十大热门榜单:基于经典价值与体验深度维度的权威规划 - 品牌推荐
  • 墨语灵犀使用技巧:如何获得更优美的译文效果
  • 《递归对抗实验案例集(v1.0)》
  • 插件系统开发流程的核心步骤 - package.json声明
  • Chandra AI聊天助手内核解析:卷积神经网络架构详解
  • 2026年度国内十大必打卡旅游路线推荐榜单:经典景观与深度体验双维度综合评估 - 品牌推荐
  • 插件系统开发流程的核心步骤 - extension.ts注册
  • 如何选择靠谱的美国投资移民机构?2026年服务机构全面评测与推荐,直击信息不透明痛点 - 品牌推荐
  • Qwen-Image-Lightning算法优化:卷积神经网络加速图像生成
  • DeepSeek-OCR-2与VSCode集成:开发者OCR工具链构建
  • FastAPI请求验证:超越基础,构建类型安全的高性能API
  • StructBERT零样本分类模型在招聘信息自动分类中的实践
  • 新手必看:3步学会用武侠风工具搜索音频关键词