在 Android 开发中,文件下载是网络编程的常见需求,通常通过 HTTP GET 请求从服务器下载文件(如图片、音频、视频、PDF 等)并保存到设备存储中。
Android 提供了多种方式实现文件下载,包括原生的
HttpURLConnection
和第三方库如 OkHttp 和 Retrofit 。
以下是 Android 文件下载的详细学习指南,重点介绍使用HttpURLConnection,并涵盖代码示例、注意事项和实践建议。
一、文件下载概述 目标 : 从服务器下载文件并保存到本地存储(如内部存储或 SD 卡)。
常见场景 : 下载图片、视频、APK 文件或文档。
支持断点续传(Range 请求)。
显示下载进度(大文件下载)。
协议 :通常使用 HTTP GET 请求,结合
Content-Length
和
Range
头。
工具 : HttpURLConnection :Android 原生,适合简单下载。
OkHttp :支持异步和进度监听,性能优越。
Retrofit :适合 RESTful API 下载,简化代码。
DownloadManager :系统提供的下载管理器,适合简单场景。
关键点 : 异步处理,避免阻塞主线程。
处理文件存储权限(Android 6.0+)。
支持大文件下载和进度反馈。
确保资源释放和错误处理。
二、准备工作 权限声明 : 在
AndroidManifest.xml
中添加网络和存储权限:
Android 6.0+ :动态请求存储权限。
Android 10+ :推荐使用
Scoped Storage
,通过
MediaStore
或指定路径保存文件。
文件保存路径 : 内部存储 :
context.getFilesDir()
或
context.getCacheDir()
。
外部存储 :
/sdcard/Download/
或
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
。
MediaStore (Android 10+):用于图片、视频等媒体文件。
服务器要求 : 提供文件的下载 URL(如
https://example.com/file.jpg
)。
支持
Range
请求头(用于断点续传)。
返回
Content-Length
头以获取文件大小。
三、使用 HttpURLConnection 实现文件下载
HttpURLConnection
是 Android 原生的 HTTP 客户端,适合简单文件下载。
以下是详细实现步骤和代码示例。
1. 基本文件下载 下载文件并保存到本地存储。
代码示例 :
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class FileDownloader {
public static String downloadFile(String fileUrl, String savePath) {
HttpURLConnection conn = null;
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
// 1. 创建连接
URL url = new URL(fileUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setRequestProperty("Accept", "*/*");
// 2. 检查响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 3. 获取输入流
inputStream = conn.getInputStream();
File file = new File(savePath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
// 4. 保存文件
outputStream = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
return "Download completed: " + savePath;
} else {
return "Error: HTTP " + responseCode;
}
} catch (Exception e) {
e.printStackTrace();
return "Exception: " + e.getMessage();
} finally {
try {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
if (conn != null) conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
使用示例 (异步调用):
new Thread(() -> {
String fileUrl = "https://example.com/image.jpg";
String savePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/image.jpg";
String result = FileDownloader.downloadFile(fileUrl, savePath);
runOnUiThread(() -> {
Log.d("Download", result);
});
}).start();
说明 : 使用
GET
请求获取文件流。
将输入流写入本地文件。
确保在子线程执行网络操作。
2. 下载进度监听 对于大文件,显示下载进度是常见需求。
代码示例 :
public class FileDownloader {
public interface ProgressListener {
void onProgress(int progress); // 进度百分比
}
public static String downloadFileWithProgress(String fileUrl, String savePath, ProgressListener listener) {
HttpURLConnection conn = null;
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
URL url = new URL(fileUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 获取文件总大小
long totalSize = conn.getContentLengthLong();
inputStream = conn.getInputStream();
File file = new File(savePath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
outputStream = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int bytesRead;
long downloadedSize = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
downloadedSize += bytesRead;
if (totalSize > 0) {
int progress = (int) (downloadedSize * 100 / totalSize);
if (listener != null) {
listener.onProgress(progress);
}
}
}
outputStream.flush();
return "Download completed: " + savePath;
} else {
return "Error: HTTP " + responseCode;
}
} catch (Exception e) {
e.printStackTrace();
return "Exception: " + e.getMessage();
} finally {
try {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
if (conn != null) conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
使用示例 :
new Thread(() -> {
String fileUrl = "https://example.com/video.mp4";
String savePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/video.mp4";
String result = FileDownloader.downloadFileWithProgress(fileUrl, savePath, progress -> {
runOnUiThread(() -> {
Log.d("Download", "Progress: " + progress + "%");
// 更新 UI,例如 ProgressBar
});
});
runOnUiThread(() -> {
Log.d("Download", result);
});
}).start();
说明 : 通过
conn.getContentLengthLong()
获取文件大小。
计算下载进度(
downloadedSize / totalSize
)。
通过回调通知 UI 线程更新进度。
3. 断点续传 断点续传通过
Range
请求头继续下载未完成的部分。
代码示例 :
public class FileDownloader {
public static String downloadFileWithResume(String fileUrl, String savePath, ProgressListener listener) {
HttpURLConnection conn = null;
InputStream inputStream = null;
RandomAccessFile randomAccessFile = null;
File file = new File(savePath);
long downloadedSize = file.exists() ? file.length() : 0;
try {
URL url = new URL(fileUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
// 设置 Range 头
if (downloadedSize > 0) {
conn.setRequestProperty("Range", "bytes=" + downloadedSize + "-");
}
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_PARTIAL) {
long totalSize = conn.getContentLengthLong() + downloadedSize;
inputStream = conn.getInputStream();
randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(downloadedSize);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
randomAccessFile.write(buffer, 0, bytesRead);
downloadedSize += bytesRead;
if (totalSize > 0) {
int progress = (int) (downloadedSize * 100 / totalSize);
if (listener != null) {
listener.onProgress(progress);
}
}
}
return "Download completed: " + savePath;
} else {
return "Error: HTTP " + responseCode;
}
} catch (Exception e) {
e.printStackTrace();
return "Exception: " + e.getMessage();
} finally {
try {
if (inputStream != null) inputStream.close();
if (randomAccessFile != null) randomAccessFile.close();
if (conn != null) conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
使用示例 :
new Thread(() -> {
String fileUrl = "https://example.com/video.mp4";
String savePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/video.mp4";
String result = FileDownloader.downloadFileWithResume(fileUrl, savePath, progress -> {
runOnUiThread(() -> {
Log.d("Download", "Progress: " + progress + "%");
});
});
runOnUiThread(() -> {
Log.d("Download", result);
});
}).start();
说明 : 使用
Range
头指定下载起始位置(
bytes=downloadedSize-
)。
使用
RandomAccessFile
支持追加写入。
服务器需支持
206 Partial Content
响应。
四、结合 DownloadManager(简单场景) Android 提供的
DownloadManager
是系统级下载工具,适合简单文件下载。
代码示例 :
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
public void downloadWithDownloadManager(Context context, String fileUrl, String fileName) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(fileUrl))
.setTitle(fileName)
.setDescription("Downloading file")
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
downloadManager.enqueue(request);
}
使用示例 :
downloadWithDownloadManager(this, "https://example.com/image.jpg", "image.jpg");
说明 : 自动显示下载通知。
不支持自定义进度监听或断点续传。
适合简单下载任务。
五、注意事项 异步处理 : 下载操作必须在子线程执行,避免
NetworkOnMainThreadException
. 使用 Thread、AsyncTask(已废弃)或 Kotlin 协程。
权限管理 : Android 6.0+:动态请求
WRITE_EXTERNAL_STORAGE
权限。
Android 10+:优先使用
MediaStore
或内部存储:
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, "image.jpg");
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
Uri uri = context.getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
错误处理 : 处理
IOException
和 HTTP 错误状态码(4xx、5xx)。
示例:
if (responseCode != HttpURLConnection.HTTP_OK && responseCode != HttpURLConnection.HTTP_PARTIAL) {
InputStream errorStream = conn.getErrorStream();
String error = new BufferedReader(new InputStreamReader(errorStream)).lines().collect(Collectors.joining("\n"));
Log.e("Error", error);
}
资源释放 : 关闭
InputStream
、
OutputStream
和
HttpURLConnection
。
文件大小与性能 : 检查
Content-Length
是否有效(可能为 -1)。
使用缓冲区(如 4KB)读取流,优化性能。
对于大文件,考虑断点续传或分片下载。
安全性 : 使用 HTTPS 确保数据安全。
验证服务器证书,避免信任所有证书。
添加认证头(如
Authorization: Bearer token
)。
六、学习建议与实践 学习路径 : 了解 HTTP GET 请求和
Content-Length
、
Range
头。
掌握
HttpURLConnection
基本下载流程。
实现进度监听和断点续传。
学习 OkHttp 或 Retrofit 替代
HttpURLConnection
。
探索
DownloadManager
简单场景。
实践项目 : 简单项目 :下载图片并保存到
Downloads
目录。
进阶项目 :实现视频下载,支持进度显示。
高级项目 :支持断点续传,保存下载进度。
调试工具 : 使用 Postman 测试下载 URL,验证响应。
使用 Charles 或 Fiddler 抓包,分析 HTTP 请求。
使用 Android Studio 的 Network Profiler 查看请求详情. 推荐资源 : Android 官方文档:https://developer.android.com/reference/java/net/HttpURLConnection MDN HTTP 文档:https://developer.mozilla.org/en-US/docs/Web/HTTP 实践 URL:测试文件下载(如 https://speedtest.tele2.net/1MB.zip) 七、总结 HttpURLConnection :Android 原生,适合简单文件下载。
核心步骤 :配置 GET 请求、读取输入流、保存文件、释放资源。
功能扩展 : 进度监听:通过
Content-Length
计算进度。
断点续传:使用
Range
头和
RandomAccessFile
。
注意事项 :异步处理、权限管理、错误处理、资源释放、HTTPS 安全。
推荐 :简单场景使用
HttpURLConnection
或
DownloadManager
,复杂场景切换到 OkHttp/Retrofit。
如果需要更详细的代码示例(如 断点续传实现、MediaStore 存储 )或 OkHttp/Retrofit 下载方法,请告诉我!
