- 获取下载链接:从服务器获取文件的 URL。
- 发起网络请求:使用 HTTP/HTTPS 请求获取文件数据流。
- 处理响应:检查服务器返回的状态码(如 200 OK)。
- 写入文件:将获取到的数据流以二进制形式写入到设备的存储中。
- 处理进度:实时计算并更新下载进度。
- 处理错误:处理网络中断、服务器错误、存储空间不足等各种异常情况。
下面我将从简单到复杂,介绍三种主流的实现方式:

使用 HttpURLConnection (原生 API,基础)
这是 Android 最基础的网络 API,不需要引入任何第三方库,适合简单的下载需求,但代码量稍多。
核心代码示例
public class DownloadUtil {
private static final String TAG = "DownloadUtil";
public interface DownloadListener {
void onProgress(int progress);
void onSuccess(File file);
void onFailure(String error);
}
public static void downloadFile(String fileUrl, String saveDir, DownloadListener listener) {
new Thread(() -> {
InputStream is = null;
FileOutputStream fos = null;
HttpURLConnection connection = null;
try {
URL url = new URL(fileUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
connection.connect();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 获取文件总大小
int fileLength = connection.getContentLength();
is = connection.getInputStream();
// 确定保存路径
File dir = new File(saveDir);
if (!dir.exists()) {
dir.mkdirs();
}
File outputFile = new File(dir, "downloaded_file_" + System.currentTimeMillis() + ".apk"); // 假设下载APK
fos = new FileOutputStream(outputFile);
int len;
int total = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
total += len;
// 计算进度并回调 (注意:不要在子线程更新UI)
int progress = (int) ((total * 1.0 / fileLength) * 100);
// 使用Handler或其他方式将进度更新到UI线程
notifyProgress(listener, progress);
}
// 下载完成
notifySuccess(listener, outputFile);
} else {
notifyFailure(listener, "Server returned HTTP " + responseCode);
}
} catch (IOException e) {
notifyFailure(listener, "Download failed: " + e.getMessage());
e.printStackTrace();
} finally {
// 关闭流和连接
try {
if (is != null) is.close();
if (fos != null) fos.close();
if (connection != null) connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
// 使用Handler在UI线程回调
private static final Handler handler = new Handler(Looper.getMainLooper());
private static void notifyProgress(DownloadListener listener, int progress) {
handler.post(() -> {
if (listener != null) {
listener.onProgress(progress);
}
});
}
private static void notifySuccess(DownloadListener listener, File file) {
handler.post(() -> {
if (listener != null) {
listener.onSuccess(file);
}
});
}
private static void notifyFailure(DownloadListener listener, String error) {
handler.post(() -> {
if (listener != null) {
listener.onFailure(error);
}
});
}
}
使用示例
// 在Activity或Fragment中
DownloadUtil.downloadFile(
"https://example.com/path/to/your/app.apk",
getExternalFilesDir(null).getAbsolutePath(), // 下载到应用私有目录
new DownloadUtil.DownloadListener() {
@Override
public void onProgress(int progress) {
// 更新UI,例如设置ProgressBar
progressBar.setProgress(progress);
tvProgress.setText(progress + "%");
}
@Override
public void onSuccess(File file) {
// 下载成功,可以提示用户或打开文件
Toast.makeText(MainActivity.this, "Downloaded to: " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(String error) {
// 下载失败
Toast.makeText(MainActivity.this, "Error: " + error, Toast.LENGTH_SHORT).show();
}
}
);
使用 OkHttp (强烈推荐)
OkHttp 是目前 Android 开发中最流行的网络库,它更高效、更易用,并且内置了对异步请求、回调处理的支持。
添加依赖
在 app/build.gradle 文件中添加:
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0") // 使用最新版本
}
核心代码示例
OkHttp 可以直接将响应体写入文件,代码非常简洁。

public class OkHttpDownloadUtil {
private static final String TAG = "OkHttpDownload";
public interface DownloadListener {
void onProgress(int progress);
void onSuccess(File file);
void onFailure(String error);
}
public static void download(String url, String saveDir, DownloadListener listener) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
// 请求失败,在主线程回调
notifyFailure(listener, "Download failed: " + e.getMessage());
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (!response.isSuccessful()) {
notifyFailure(listener, "Server returned: " + response.code());
return;
}
// 获取输入流和文件总大小
InputStream is = response.body().byteStream();
long contentLength = response.body().contentLength();
File dir = new File(saveDir);
if (!dir.exists()) {
dir.mkdirs();
}
File outputFile = new File(dir, "downloaded_file_" + System.currentTimeMillis() + ".apk");
FileOutputStream fos = new FileOutputStream(outputFile);
byte[] buffer = new byte[1024];
int len;
long total = 0;
try {
while ((len = is.read(buffer)) != -1) {
total += len;
fos.write(buffer, 0, len);
// 计算进度
if (contentLength > 0) {
int progress = (int) ((total * 1.0 / contentLength) * 100);
notifyProgress(listener, progress);
}
}
fos.flush();
// 下载成功
notifySuccess(listener, outputFile);
} catch (IOException e) {
// 如果发生异常,删除可能不完整的文件
if (outputFile.exists()) {
outputFile.delete();
}
notifyFailure(listener, "Write file failed: " + e.getMessage());
} finally {
try {
if (is != null) is.close();
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
// 同样需要Handler在UI线程回调
private static final Handler handler = new Handler(Looper.getMainLooper());
// ... (notifyProgress, notifySuccess, notifyFailure 方法与上面HttpURLConnection示例中相同) ...
}
使用示例
与 HttpURLConnection 的使用方式几乎完全相同,只是调用方法名不同。
使用 DownloadManager (系统服务,推荐用于大文件)
对于系统级的、大文件的下载,Android 提供了一个强大的系统服务 DownloadManager,它会处理很多底层细节,如:
- 在后台下载,即使应用关闭。
- 系统重启后自动恢复下载。
- 处理网络切换。
- 提供系统下载通知栏。
- 自动处理重试逻辑。
添加权限
在 AndroidManifest.xml 中添加必要的权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 如果目标SDK是33 (Android 13) 或更高,需要这个权限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- 如果需要下载到公共目录,需要这个权限,但更推荐使用应用私有目录 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
核心代码示例
public class DownloadManagerUtil {
public static void downloadWithSystemManager(Context context, String url, String fileName) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
// 设置下载的保存位置
// 1. 下载到应用私有目录(不需要READ/WRITE_EXTERNAL_STORAGE权限,更安全)
File outputDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
if (outputDir == null) {
Toast.makeText(context, "Cannot access external storage", Toast.LENGTH_SHORT).show();
return;
}
File outputFile = new File(outputDir, fileName);
request.setDestinationUri(Uri.fromFile(outputFile));
// 2. 下载到公共下载目录(需要权限)
// request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
// 设置通知的标题和描述
request.setTitle("Downloading File");
request.setDescription("Downloading " + fileName);
// 设置为可见下载,并在通知栏显示进度
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
// 允许在移动网络和Wi-Fi上下文中下载
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);
// 获取系统DownloadManager服务
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadManager != null) {
long downloadId = downloadManager.enqueue(request);
// 可以保存这个downloadId,之后用它来查询下载状态
Log.d("DownloadManager", "Download started with ID: " + downloadId);
}
}
// 如何查询下载状态?
public static void checkDownloadStatus(Context context, long downloadId) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadManager != null) {
Cursor cursor = downloadManager.query(query);
if (cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
switch (status) {
case DownloadManager.STATUS_PAUSED:
Log.d("DownloadStatus", "Download is paused");
break;
case DownloadManager.STATUS_PENDING:
Log.d("DownloadStatus", "Download is pending");
break;
case DownloadManager.STATUS_RUNNING:
Log.d("DownloadStatus", "Download is in progress");
// 可以在这里获取进度
int bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
int totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
int progress = (int) ((bytesDownloaded * 100) / (double) totalBytes);
Log.d("DownloadStatus", "Progress: " + progress + "%");
break;
case DownloadManager.STATUS_SUCCESSFUL:
Log.d("DownloadStatus", "Download is completed");
String localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
Log.d("DownloadStatus", "File saved at: " + localUri);
break;
case DownloadManager.STATUS_FAILED:
Log.d("DownloadStatus", "Download has failed");
break;
}
}
cursor.close();
}
}
}
总结与对比
| 特性 | HttpURLConnection |
OkHttp | DownloadManager |
|---|---|---|---|
| 依赖 | 无需 (Android原生) | 需添加 okhttp 库 |
无需 (Android原生) |
| 易用性 | 中等,代码量较多 | 高,API简洁 | 简单,但配置项较多 |
| 灵活性 | 高,可完全控制下载过程 | 高,可完全控制下载过程 | 低,功能固定 |
| 后台下载 | 不支持,应用关闭会中断 | 不支持,应用关闭会中断 | 支持,应用关闭甚至重启后仍可继续 |
| 通知栏 | 需自己实现 | 需自己实现 | 自动提供 |
| 断点续传 | 需自己实现 (设置 Range 请求头) |
需自己实现 (设置 Range 请求头) |
自动支持 |
| 大文件处理 | 不推荐,容易ANR | 推荐,有异步回调 | 强烈推荐,专为后台大文件设计 |
| 适用场景 | 简单、小文件下载,或不想引入第三方库时 | 通用场景,几乎所有网络请求 | 下载APK、大文件、需要系统级通知和断点续传的场景 |
最佳实践建议
-
权限管理:
(图片来源网络,侵删)- Android 10 (API 29) 及以上,默认情况下应用只能访问自己的外部存储目录 (
Context.getExternalFilesDir()),无需申请WRITE_EXTERNAL_STORAGE权限,这是最推荐的做法。 - 如果必须访问公共目录,需要声明
MANAGE_EXTERNAL_STORAGE权限,并且需要向用户解释原因。
- Android 10 (API 29) 及以上,默认情况下应用只能访问自己的外部存储目录 (
-
线程管理:
- 所有网络操作都不能在主线程(UI线程)中进行,否则会抛出
NetworkOnMainThreadException异常,使用Thread,AsyncTask(已废弃),ExecutorService或 OkHttp 自带的异步回调。
- 所有网络操作都不能在主线程(UI线程)中进行,否则会抛出
-
断点续传:
- 如果需要实现断点续传,你需要手动处理,做法是:
- 首先检查本地文件是否存在。
- 如果存在,获取文件当前大小。
- 在发起 HTTP 请求时,添加
Range请求头,Range: bytes=已下载大小-。 - 服务器需要支持
206 Partial Content响应。 - 下载时,使用
RandomAccessFile以追加模式 ("rw") 写入文件,而不是覆盖。
- 如果需要实现断点续传,你需要手动处理,做法是:
-
安全性:
强烈建议使用 HTTPS 协议下载文件,以防止中间人攻击和数据篡改。
对于大多数现代 Android 应用,OkHttp 是最平衡、最灵活的选择,如果你需要下载系统级的大文件(如应用更新包),DownloadManager 是不二之选。HttpURLConnection 则适合在不想引入任何第三方库的简单项目中使用。
