核心概念
无论使用哪种方法,基本的网络请求流程都遵循以下步骤:

- 添加网络权限:在
AndroidManifest.xml中声明允许应用使用网络。 - 执行网络请求:在后台线程(不能是主线程/UI线程)向服务器发送请求。
- 处理响应:读取服务器返回的数据(文件流)。
- 保存文件:将读取到的数据写入到设备的存储中。
- 更新 UI:在主线程上通知用户操作结果(显示下载进度、成功或失败提示)。
使用 HttpURLConnection (原生 API)
这是 Java 标准库自带的类,无需添加任何第三方依赖,对于简单的下载任务,它完全够用。
添加权限
<!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 如果需要下载到外部存储,还需要这个权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 对于 Android 11 (API 30) 及以上,建议使用 MANAGE_EXTERNAL_STORAGE --> <!-- 但最好遵循更现代的分区存储方案 -->
创建下载工具类
这是一个使用 HttpURLConnection 并带有进度回调的完整示例。
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 interface DownloadCallback {
void onProgress(int progress); // 更新进度
void onSuccess(File file); // 下载成功
void onFailure(Exception e); // 下载失败
}
public static void downloadFile(Context context, String fileUrl, String fileName, DownloadCallback callback) {
// 必须在后台线程执行
new Thread(() -> {
InputStream inputStream = null;
FileOutputStream outputStream = null;
HttpURLConnection connection = null;
try {
URL url = new URL(fileUrl);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
// 检查是否是重定向
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException("Server returned HTTP " + connection.getResponseCode()
+ " " + connection.getResponseMessage());
}
// 获取文件总大小
int fileLength = connection.getContentLength();
// 创建目标文件
// 注意:Android 10+ 推荐使用 context.getExternalFilesDir() 获取应用私有目录
File outputDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
if (outputDir == null) {
throw new IOException("External storage is not available");
}
File outputFile = new File(outputDir, fileName);
inputStream = connection.getInputStream();
outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[4096];
int bytesRead;
int totalBytesRead = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
// 计算并上报进度
if (fileLength > 0) { // 避免除以零
int progress = (int) ((totalBytesRead * 100) / fileLength);
// 切换到主线程更新UI
((MainActivity) context).runOnUiThread(() -> callback.onProgress(progress));
}
}
// 下载完成
((MainActivity) context).runOnUiThread(() -> callback.onSuccess(outputFile));
} catch (Exception e) {
// 发生错误
((MainActivity) context).runOnUiThread(() -> callback.onFailure(e));
} finally {
// 确保流和连接被关闭
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();
}
}
}).start();
}
}
在 Activity 中使用
// 假设你的 Activity 是 MainActivity
public class MainActivity extends AppCompatActivity {
private static final String FILE_URL = "http://example.com/path/to/your/file.pdf";
private static final String FILE_NAME = "downloaded_file.pdf";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button downloadButton = findViewById(R.id.download_button);
ProgressBar progressBar = findViewById(R.id.progress_bar);
TextView statusText = findViewById(R.id.status_text);
downloadButton.setOnClickListener(v -> {
downloadButton.setEnabled(false);
progressBar.setProgress(0);
statusText.setText("下载中...");
FileDownloader.downloadFile(this, FILE_URL, FILE_NAME, new FileDownloader.DownloadCallback() {
@Override
public void onProgress(int progress) {
progressBar.setProgress(progress);
statusText.setText("下载中... " + progress + "%");
}
@Override
public void onSuccess(File file) {
runOnUiThread(() -> {
statusText.setText("下载完成: " + file.getAbsolutePath());
downloadButton.setEnabled(true);
// 可以在这里打开文件,例如使用 Intent
openFile(file);
});
}
@Override
public void onFailure(Exception e) {
runOnUiThread(() -> {
statusText.setText("下载失败: " + e.getMessage());
downloadButton.setEnabled(true);
Log.e("MainActivity", "Download failed", e);
});
}
});
});
}
private void openFile(File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = FileProvider.getUriForFile(this, "com.your.package.name.fileprovider", file);
intent.setDataAndType(uri, "application/pdf"); // 根据文件类型设置 MimeType
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
startActivity(intent);
} catch (Exception e) {
Toast.makeText(this, "没有找到可以打开此文件的应用", Toast.LENGTH_SHORT).show();
}
}
}
使用 OkHttp (强烈推荐)
OkHttp 是目前 Android 开发中最流行的网络请求库,它更高效、功能更强大,并且能更好地处理现代网络(如 HTTP/2)。
添加依赖
在 app/build.gradle 文件中添加:

dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0") // 使用最新版本
}
使用 OkHttp 下载文件
OkHttp 的实现方式更简洁,通常结合 ResponseBody 和 byteStream() 来高效地处理大文件流。
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class OkHttpFileDownloader {
private static final String TAG = "OkHttpDownloader";
public interface DownloadCallback {
void onProgress(int progress);
void onSuccess(File file);
void onFailure(Exception e);
}
public static void downloadFile(Context context, String fileUrl, String fileName, DownloadCallback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(fileUrl).build();
client.newCall(request).enqueue(new okhttp3.Callback() { // enqueue 使其在后台线程自动执行
@Override
public void onFailure(okhttp3.Call call, IOException e) {
// 在失败回调中,切换到主线程更新UI
((MainActivity) context).runOnUiThread(() -> callback.onFailure(e));
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
((MainActivity) context).runOnUiThread(() -> callback.onFailure(new IOException("Unexpected code " + response)));
return;
}
ResponseBody body = response.body();
if (body == null) {
((MainActivity) context).runOnUiThread(() -> callback.onFailure(new IOException("Response body is null")));
return;
}
long contentLength = body.contentLength();
InputStream inputStream = body.byteStream();
File outputDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
File outputFile = new File(outputDir, fileName);
FileOutputStream outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[4096];
int bytesRead;
long totalBytesRead = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
if (contentLength > 0) {
int progress = (int) ((totalBytesRead * 100) / contentLength);
((MainActivity) context).runOnUiThread(() -> callback.onProgress(progress));
}
}
outputStream.close();
inputStream.close();
// 下载成功,切换到主线程
((MainActivity) context).runOnUiThread(() -> callback.onSuccess(outputFile));
}
});
}
}
使用方式和 HttpURLConnection 版本几乎一样,只需替换工具类即可。
使用 Android DownloadManager (系统服务)
对于下载任务,特别是大文件,Android 系统提供了专门的 DownloadManager,它是一个系统服务,可以处理下载、断点续传、通知显示等,非常强大且省电。
添加权限
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 需要一个接收下载完成的广播接收器 -->
<application ...>
<receiver
android:name=".DownloadCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
</receiver>
</application>
启动下载
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
public class SystemDownloader {
public static void startDownload(Context context, String fileUrl, String fileName) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(fileUrl));
// 设置下载的保存位置
// 使用外部公共目录,文件可以被其他应用访问
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
// 可选:设置网络类型为Wi-Fi
// request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// 可选:显示下载通知
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
// 获取DownloadManager服务并 enqueue 下载任务
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadManager != null) {
downloadManager.enqueue(request);
}
}
}
创建广播接收器来处理下载完成事件
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class DownloadCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (downloadId != -1) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadManager != null) {
android.database.Cursor cursor = downloadManager.query(query);
if (cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
String uriString = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
Toast.makeText(context, "下载完成: " + uriString, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
}
}
cursor.close();
}
}
}
}
总结与对比
| 特性 | HttpURLConnection |
OkHttp | DownloadManager |
|---|---|---|---|
| 依赖 | 无需 (Java标准库) | 需添加 okhttp 库 |
无需 (Android系统服务) |
| 灵活性 | 高,可精细控制每个请求 | 非常高,支持拦截器、异步/同步 | 低,功能固定 |
| 大文件处理 | 需手动管理流和线程 | 非常优秀,专为流式传输设计 | 非常优秀,系统级优化,支持断点续传 |
| 用户体验 | 需手动实现进度条和通知 | 需手动实现进度条和通知 | 自动,提供系统通知栏进度 |
| 后台任务 | 需手动使用 Thread 或 AsyncTask |
enqueue() 自动处理 |
系统服务,独立于App生命周期 |
| 适用场景 | 简单、小文件下载;不想引入第三方库 | 推荐,大多数网络请求场景,包括大文件下载 | 大文件下载;需要系统通知和断点续传;希望降低App电量消耗 |
如何选择?

- 新手或简单任务:使用
HttpURLConnection足够,能帮助你理解网络请求的基本原理。 - 生产环境开发:强烈推荐使用 OkHttp,它是行业标准,功能强大、性能优异、社区活跃。
- 下载大型文件:优先考虑
DownloadManager,它能提供最佳的用户体验(系统通知)和系统级的稳定性、省电优化,如果还需要在下载过程中显示自定义进度条,则可以结合 OkHttp 或HttpURLConnection使用。
