核心概念
无论使用哪种方法,下载文件的核心步骤都大同小异:

- 获取网络连接:通过
URL对象建立与远程文件的连接。 - 获取输入流:从连接中获取
InputStream,用于读取网络数据。 - 获取输出流:创建一个
FileOutputStream,用于将数据写入本地存储。 - 读写数据:使用一个缓冲区(如
byte[]),循环从输入流读取数据,并写入输出流。 - 关闭资源:在
finally块或使用try-with-resources语句中,确保所有流和连接都被正确关闭,防止内存泄漏。 - 处理UI更新:在主线程(UI线程)中更新进度条、提示文本等UI元素。
使用 HttpURLConnection (原生、基础)
这是 Java 标准库中自带的 HTTP 客户端,无需添加任何依赖,适合简单的下载任务。
优点:
- 无需额外依赖,是 Android SDK 的一部分。
- 代码直观,易于理解。
缺点:
- API 较为底层,需要处理很多细节(如连接、流、错误码)。
- 不支持现代的 HTTP/2 协议。
- 实现起来代码量稍多。
完整代码示例
添加网络权限
在 AndroidManifest.xml 中添加:
<uses-permission android:name="android.permission.INTERNET" /> <!-- 如果要下载到外部存储,还需要这个权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 对于 Android 10 (API 29) 及以上,推荐使用 scoped storage,而不是直接申请 WRITE_EXTERNAL_STORAGE -->
创建下载工具类

import android.content.Context;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class FileDownloader {
private static final String TAG = "FileDownloader";
// 下载文件
public static void downloadFile(String fileUrl, String fileName, DownloadCallback callback) {
InputStream inputStream = null;
FileOutputStream outputStream = null;
HttpURLConnection connection = null;
try {
// 1. 创建 URL 和连接
URL url = new URL(fileUrl);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
// 检查是否是成功的响应码 (200 OK)
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException("Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage());
}
// 2. 获取输入流
inputStream = connection.getInputStream();
// 3. 确定保存路径 (这里保存到外部公共下载目录)
// 注意: Android 10+ 推荐使用 Context.getExternalFilesDir() 或 MediaStore API
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
if (!directory.exists()) {
directory.mkdirs();
}
File outputFile = new File(directory, fileName);
// 4. 获取输出流
outputStream = new FileOutputStream(outputFile);
// 5. 读写数据
byte[] buffer = new byte[4096]; // 4KB 缓冲区
int bytesRead;
long totalBytesRead = 0;
long fileSize = connection.getContentLength(); // 获取文件总大小
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// 6. 回调进度
if (callback != null) {
int progress = (int) ((totalBytesRead * 100) / fileSize);
callback.onProgressUpdate(progress);
}
}
// 下载完成
if (callback != null) {
callback.onSuccess(outputFile.getAbsolutePath());
}
} catch (IOException e) {
Log.e(TAG, "Download failed", e);
if (callback != null) {
callback.onError(e.getMessage());
}
} finally {
// 7. 关闭所有资源
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
Log.e(TAG, "Error closing output stream", e);
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
Log.e(TAG, "Error closing input stream", e);
}
}
if (connection != null) {
connection.disconnect();
}
}
}
// 下载回调接口
public interface DownloadCallback {
void onProgressUpdate(int progress);
void onSuccess(String filePath);
void onError(String errorMessage);
}
}
在 Activity 或 Fragment 中调用
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
public class MainActivity extends AppCompatActivity implements FileDownloader.DownloadCallback {
private ProgressBar progressBar;
private TextView tvProgress;
private Button btnDownload;
private static final String DOWNLOAD_URL = "https://example.com/path/to/your/file.zip";
private static final String FILE_NAME = "downloaded_file.zip";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressBar);
tvProgress = findViewById(R.id.tvProgress);
btnDownload = findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在后台线程执行下载
new Thread(new Runnable() {
@Override
public void run() {
FileDownloader.downloadFile(DOWNLOAD_URL, FILE_NAME, MainActivity.this);
}
}).start();
}
});
}
@Override
public void onProgressUpdate(int progress) {
// 更新UI必须在主线程
runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setProgress(progress);
tvProgress.setText("下载进度: " + progress + "%");
}
});
}
@Override
public void onSuccess(String filePath) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "下载成功! 文件保存在: " + filePath, Toast.LENGTH_LONG).show();
btnDownload.setText("下载完成");
}
});
}
@Override
public void onError(String errorMessage) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "下载失败: " + errorMessage, Toast.LENGTH_SHORT).show();
btnDownload.setText("重新下载");
}
});
}
}
使用第三方库 (推荐)
手动实现下载逻辑容易出错,且功能有限(如断点续传、并发下载),在实际开发中,强烈推荐使用成熟的第三方库。
推荐库:
- OkHttp + Retrofit:现代 Android 开发的标准组合,OkHttp 专注于网络请求,Retrofit 对其进行封装,提供更友好的 API。
- Android Download Manager:一个系统服务,专为下载任务设计,它处理了线程、通知、断点续传等复杂逻辑,非常适合下载大文件。
方式二.1:使用 OkHttp 下载
OkHttp 的 ResponseBody 提供了 byteStream() 方法,可以方便地获取输入流,与 HttpURLConnection 的实现方式非常相似,但代码更简洁。
添加依赖
在 app/build.gradle 中添加:
implementation 'com.squareup.okhttp3:okhttp:4.9.3' // 使用最新版本
创建下载方法
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class OkHttpDownloader {
private static final String TAG = "OkHttpDownloader";
public static void downloadWithOkHttp(String fileUrl, String fileName, DownloadCallback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(fileUrl).build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
// 获取响应体
ResponseBody body = response.body();
if (body == null) {
throw new IOException("Response body is null");
}
long fileSize = body.contentLength();
InputStream inputStream = body.byteStream();
// ... (保存文件的逻辑与 HttpURLConnection 示例中的第3,4,5,6步完全相同) ...
// 这里为了简洁,省略了重复代码,实际项目中应复用或封装。
} catch (IOException e) {
e.printStackTrace();
if (callback != null) {
callback.onError(e.getMessage());
}
}
}
// 回调接口与之前相同
public interface DownloadCallback {
void onProgressUpdate(int progress);
void onSuccess(String filePath);
void onError(String errorMessage);
}
}
方式二.2:使用 Android Download Manager (系统服务)
这是处理大文件下载最简单、最高效的方式,它会自动在后台处理下载,并在通知栏显示进度。
添加权限
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 10+ 处理同上 -->
代码实现
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.widget.Toast;
public class DownloadManagerHelper {
public static void startDownload(Context context, String fileUrl, String fileName) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(fileUrl));
// 设置下载的保存位置
// 对于 Android 10+,推荐使用 getExternalFilesDir()
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
// 设置网络类型
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE);
// 显示通知
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
// 设置标题
request.setTitle("文件下载");
request.setDescription("正在下载: " + fileName);
// 获取 DownloadManager 服务并启动下载
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadManager != null) {
downloadManager.enqueue(request);
Toast.makeText(context, "下载已开始", Toast.LENGTH_SHORT).show();
}
}
}
优点:
- 简单:几行代码即可启动下载。
- 强大:自动处理断点续传、并发下载、网络切换。
- 用户体验好:自动在通知栏显示下载进度。
缺点:
- 灵活性低:难以在自定义 UI(如 Activity 进度条)中实时获取精确的下载进度。
- 依赖系统:不同 Android 版本的行为可能略有不同。
最佳实践与注意事项
-
不能在主线程进行网络操作 Android 4.0 (API 13) 之后,禁止在主线程执行网络请求,否则会抛出
NetworkOnMainThreadException,必须使用子线程、AsyncTask、ExecutorService、RxJava或Kotlin Coroutines来处理。 -
处理存储权限
- Android 10 (API 29) 及以上:引入了 Scoped Storage,直接访问
Environment.getExternalStoragePublicDirectory()可能会受限,推荐使用Context.getExternalFilesDir()目录,它不需要WRITE_EXTERNAL_STORAGE权限,但应用卸载后数据会被删除,如果需要公共访问,应使用MediaStoreAPI。 - Android 11 (API 30) 及以上:对
MANAGE_EXTERNAL_STORAGE权限的申请更加严格。
- Android 10 (API 29) 及以上:引入了 Scoped Storage,直接访问
-
处理网络变化 在下载过程中,网络可能会断开。
HttpURLConnection和OkHttp默认会抛出异常,你需要捕获并处理。DownloadManager会自动处理网络切换和断点续传。 -
UI 更新 任何时候都不要在子线程中更新 UI,必须使用
Activity.runOnUiThread(),View.post(),Handler等方式将 UI 操作切换回主线程。 -
资源释放
InputStream和OutputStream是宝贵的系统资源,一定要确保它们在使用后被关闭,最佳实践是使用try-with-resources语句,它能自动关闭实现了AutoCloseable接口的对象。
总结与选择
| 特性 | HttpURLConnection |
OkHttp | Android Download Manager |
|---|---|---|---|
| 易用性 | 低 | 中 | 高 |
| 灵活性 | 高 | 高 | 低 |
| 功能 | 基础 | 强大 (拦截器等) | 强大 (系统级) |
| 断点续传 | 需手动实现 | 需手动实现 | 自动 |
| UI 集成 | 容易获取进度 | 容易获取进度 | 困难 |
| 依赖 | 无需 | 需添加 OkHttp 依赖 | 无需 (系统服务) |
- 新手学习/简单任务:从
HttpURLConnection开始,有助于理解底层原理。 - 常规 App 开发:强烈推荐 OkHttp,它是现代网络请求的事实标准,灵活且强大。
- 下载大文件/追求简单和稳定性:强烈推荐 Android Download Manager,它能让你从复杂的下载逻辑中解放出来,专注于用户体验。
