核心思路
无论使用哪种方法,下载文件的基本流程都遵循以下步骤:

- 建立网络连接:通过 HTTP/HTTPS 协议连接到服务器上的文件 URL。
- 发起请求:发送一个
GET请求来获取文件。 - 获取输入流:从服务器的响应中获取一个输入流 (
InputStream)。 - 写入本地存储:创建一个文件输出流 (
FileOutputStream),将输入流中的数据分块读取出来,并写入到设备的外部或内部存储中。 - 处理进度和结果:在下载过程中更新 UI 进度条,并在下载完成后通知用户。
使用 HttpURLConnection (原生 API,推荐用于学习和小型项目)
这是 Android SDK 自带的网络 API,无需添加第三方依赖,适合理解网络下载的基本原理。
添加网络权限
在 app/src/main/AndroidManifest.xml 中添加:
<uses-permission android:name="android.permission.INTERNET" /> <!-- 如果需要下载到外部存储,还需要这个权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 10 (API 29) 及以上,推荐使用 MANAGE_EXTERNAL_STORAGE,但需要特殊说明 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
注意:从 Android 10 (API 29) 开始,应用默认在沙盒环境中运行,推荐使用
Context.getExternalFilesDir()目录,这个目录不需要任何权限,如果必须下载到公共目录(如Download),则需要处理运行时权限。
实现 DownloadTask
我们使用 AsyncTask (虽然已废弃,但为了简单演示) 或 ExecutorService + Handler 来在后台线程执行下载任务,避免阻塞 UI 线程,在现代 Android 开发中,更推荐使用 Kotlin Coroutines 或 RxJava。

这里我们使用 ExecutorService 和 Handler 的方式。
// 在 Activity 或 Fragment 中
private val executor = Executors.newSingleThreadExecutor()
private val handler = Handler(Looper.getMainLooper())
fun downloadFile(urlString: String, destinationPath: File) {
executor.execute {
var inputStream: InputStream? = null
var outputStream: FileOutputStream? = null
var connection: HttpURLConnection? = null
try {
// 1. 创建 URL 和连接
val url = URL(urlString)
connection = url.openConnection() as HttpURLConnection
connection.connect()
// 2. 检查响应码
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
throw IOException("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}")
}
// 3. 获取输入流和文件总大小
val fileLength = connection.contentLengthLong
inputStream = connection.inputStream
// 4. 创建输出流
outputStream = FileOutputStream(destinationPath)
// 5. 缓冲区读写
val buffer = ByteArray(4096) // 4KB 缓冲区
var bytesRead: Int
var totalBytesRead = 0L
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
totalBytesRead += bytesRead
// 6. 计算并发布进度
val progress = ((totalBytesRead * 100) / fileLength).toInt()
handler.post {
// 更新 UI,ProgressBar
progressBar.progress = progress
textViewProgress.text = "$progress%"
}
}
// 7. 下载完成
handler.post {
Toast.makeText(this, "下载完成!", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
handler.post {
Toast.makeText(this, "下载失败: ${e.message}", Toast.LENGTH_LONG).show()
}
} finally {
// 8. 关闭流和连接
inputStream?.close()
outputStream?.close()
connection?.disconnect()
}
}
}
使用第三方库 (强烈推荐)
手动管理网络连接、线程、进度等非常繁琐,容易出错,在实际项目中,强烈建议使用成熟的第三方库。
OkHttp + Retrofit (业界标准)
OkHttp 是一个高效的 HTTP 客户端,Retrofit 是一个类型安全的 HTTP 客户端,它们是 Android 开发的黄金搭档。
步骤:

a. 添加依赖
// build.gradle (Module :app) implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // 用于解析 JSON implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'com.squareup.okio:okio:2.8.0'
b. 定义下载服务接口
import retrofit2.http.GET
import retrofit2.http.Streaming
import okhttp3.ResponseBody
import retrofit2.Call
interface DownloadApiService {
// @Streaming 表示直接以流的形式返回,避免内存溢出
@Streaming
@GET("path/to/your/file.zip")
fun downloadFile(): Call<ResponseBody>
}
c. 创建 Retrofit 实例
private val retrofit = Retrofit.Builder()
.baseUrl("https://your-api-base-url.com/")
.build()
private val apiService = retrofit.create(DownloadApiService::class.java)
d. 执行下载
fun downloadFileWithRetrofit() {
val call = apiService.downloadFile()
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
saveFile(body)
}
} else {
Toast.makeText(this, "下载失败: ${response.message()}", Toast.LENGTH_SHORT).show()
}
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Toast.makeText(this, "下载失败: ${t.message}", Toast.LENGTH_SHORT).show()
}
})
}
private fun saveFile(body: ResponseBody) {
val filePath = File(getExternalFilesDir(null), "downloaded_file.zip")
try {
inputStream = body.byteStream()
outputStream = FileOutputStream(filePath)
val buffer = ByteArray(4096)
var bytesRead: Int
var fileSize = body.contentLength()
var totalBytesRead = 0L
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
totalBytesRead += bytesRead
val progress = ((totalBytesRead * 100) / fileSize).toInt()
// 更新 UI
}
Toast.makeText(this, "文件保存到: ${filePath.absolutePath}", Toast.LENGTH_LONG).show()
} catch (e: Exception) {
e.printStackTrace()
} finally {
inputStream?.close()
outputStream?.close()
}
}
优点:
- 代码简洁,类型安全。
- 内置了线程管理,无需手动创建线程。
@Streaming注解对于大文件下载至关重要,可以防止将整个文件加载到内存中。- 功能强大,易于扩展(如添加拦截器、Gzip 解压等)。
Android DownloadManager (系统服务,适用于简单下载)
DownloadManager 是一个系统级的服务,专门用于处理下载任务,它非常适合下载 APK、MP4 等文件,因为它会处理重试、网络状态变化、通知栏显示等复杂逻辑。
a. 添加权限
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <!-- 如果你希望自己的应用能管理下载任务 --> <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
b. 使用 DownloadManager
fun downloadWithDownloadManager() {
val uri = Uri.parse("https://your-server.com/file.zip")
val request = DownloadManager.Request(uri)
// 设置下载时的网络类型
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
// 设置下载路径和文件名
// 保存到公共下载目录
val destination = Environment.DIRECTORY_DOWNLOADS + "/my_downloaded_file.zip"
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "my_downloaded_file.zip")
// 显示下载进度在通知栏
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
// 设置标题
request.setTitle("文件下载")
request.setDescription("正在下载文件...")
// 获取 DownloadManager 服务并启动下载
val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
val downloadId = downloadManager.enqueue(request)
// (可选) 可以通过 BroadcastReceiver 监听下载完成事件
// registerReceiver(receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
优点:
- 简单易用:几行代码就能启动下载。
- 系统级管理:自动处理断点续传、网络切换、通知栏显示。
- 省电:系统可以优化下载时机。
缺点:
- 灵活性差:难以自定义进度条(只能在通知栏显示)。
- 不适合应用内集成:主要用于让用户下载文件,而不是在应用内直接使用下载的文件。
关键知识点和最佳实践
-
在子线程中执行网络操作 Android 的主线程(UI 线程)不允许执行耗时操作,否则会抛出
NetworkOnMainThreadException异常,必须使用AsyncTask,Thread,ExecutorService,Kotlin Coroutines或RxJava等方式在后台线程执行下载。 -
处理大文件与内存 (
@Streaming) 如果不使用@Streaming,Retrofit 默认会将ResponseBody的内容全部读入内存中的一个byte[],对于几百 MB 甚至 GB 的大文件,这会导致OutOfMemoryError。@Streaming保证ResponseBody以流的形式返回,你可以按需读取,从而避免内存问题。 -
权限处理 (Android 6.0+) 从 Android 6.0 (Marshmallow) 开始,敏感权限需要在运行时动态请求。
- 写入存储权限:如果目标目录是外部公共存储(如
Downloads),需要动态请求WRITE_EXTERNAL_STORAGE。 - 作用域存储 (Scoped Storage):Android 10 (API 29) 引入了作用域存储,应用默认只能访问自己的沙盒目录(如
getExternalFilesDir()),访问公共目录需要特殊处理或用户授权。
- 写入存储权限:如果目标目录是外部公共存储(如
-
断点续传 对于大文件下载,断点续传非常重要,实现方法是在请求头中添加
Range。- 初始请求:
HEAD请求或GET请求,检查服务器是否支持Accept-Ranges: bytes。 - 续传请求:如果本地已有部分文件,获取其大小,然后发送
GET请求,并设置头信息:Range: bytes=startByte-。 - OkHttp 和 Retrofit 可以方便地自定义请求头。
- 初始请求:
-
使用 WorkManager (推荐的后台任务方案) 如果你的下载任务需要在应用关闭后、设备重启后甚至系统升级后仍然能继续执行,
WorkManager是最佳选择,它是一个用于保证任务可靠执行的库,可以处理网络约束、存储约束等。
总结与选择
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| HttpURLConnection | 无需依赖,轻量级 | 代码繁琐,需手动处理线程、错误等 | 学习网络基础,或对依赖有极简要求的小型项目 |
| OkHttp + Retrofit | 业界标准,代码优雅,功能强大,类型安全,支持流式下载 | 需要添加第三方依赖 | 绝大多数 Android 应用的首选,特别是需要复杂网络交互和自定义下载逻辑时 |
| DownloadManager | 系统服务,简单易用,自动处理重试和通知 | 灵活性差,不适合应用内集成 | 下载 APK、更新包等,让用户从浏览器或通知栏获取文件的场景 |
给你的建议:
- 如果你是初学者:先用
HttpURLConnection实现一次,理解其原理。 - 如果你在开发商业应用或任何正式项目:直接使用 OkHttp + Retrofit,这是最专业、最高效、最可靠的方式。
- 如果你只是想让用户下载一个文件,不关心下载过程,也不需要在应用内立即使用它:可以考虑
DownloadManager。
