核心概念:网络请求
无论使用哪种方法,其本质都是通过 HTTP/HTTPS 协议向服务器发送一个请求,然后接收服务器返回的文件数据,在 Android 中,网络操作不能在主线程(UI线程)中执行,否则会抛出 NetworkOnMainThreadException 异常,我们必须使用异步任务或线程来处理网络请求。

使用 HttpURLConnection (Java 原生方式)
这是 Android SDK 自带的方式,无需添加第三方依赖,适合处理简单的下载任务,但对于复杂的网络交互,代码会显得比较繁琐。
适用场景:
- 简单的文件下载。
- 不想引入第三方库的项目。
实现步骤:
- 获取网络权限:在
AndroidManifest.xml中添加。 - 在后台线程中发起请求:可以使用
AsyncTask(已废弃,但适合理解)、Thread+Handler,或者现代的ExecutorService。 - 处理输入流和输出流:将服务器返回的文件流写入到本地文件。
代码示例 (使用 ExecutorService 和 FileOutputStream):
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FileDownloader {
private static final String FILE_URL = "http://example.com/path/to/your/file.pdf";
private static final String FILE_NAME = "downloaded_file.pdf";
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static final String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private ExecutorService executor = Executors.newSingleThreadExecutor();
public void downloadFile() {
// 检查权限
if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 请求权限
ActivityCompat.requestPermissions(thisActivity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
return;
}
executor.execute(() -> {
HttpURLConnection connection = null;
InputStream inputStream = null;
FileOutputStream outputStream = null;
File file = null;
try {
URL url = new URL(FILE_URL);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
// 检查HTTP响应码
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
// 获取外部存储的公共下载目录
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
if (!directory.exists()) {
directory.mkdirs();
}
file = new File(directory, FILE_NAME);
inputStream = connection.getInputStream();
outputStream = new FileOutputStream(file);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
// 下载完成,可以在主线程更新UI
runOnUiThread(() -> {
Toast.makeText(getApplicationContext(), "文件下载成功: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
});
} else {
Log.e("FileDownload", "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage());
}
} catch (IOException e) {
Log.e("FileDownload", "Error downloading file", e);
} finally {
// 关闭流和连接
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (connection != null) {
connection.disconnect();
}
}
});
}
}
使用 OkHttp (强烈推荐)
OkHttp 是目前 Android 开发中最流行的网络请求库,它更高效、更简洁,并且内置了对现代网络协议(如 HTTP/2)的支持,处理文件下载非常方便。
适用场景:
- 几乎所有需要网络请求的 Android 应用。
- 需要处理复杂的网络逻辑(如缓存、拦截器)。
实现步骤:
- 添加依赖:在
app/build.gradle文件中添加。 - 获取网络权限:同上。
- 创建 OkHttp 客户端。
- 构建请求并执行。
- 使用
ResponseBody将字节流写入文件。
代码示例:
添加依赖 (build.gradle):
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0") // 使用最新版本
}
下载代码:

import android.Manifest;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
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;
public class OkHttpFileDownloader {
private static final String FILE_URL = "http://example.com/path/to/your/image.jpg";
private static final String FILE_NAME = "downloaded_image.jpg";
private OkHttpClient client = new OkHttpClient();
public void downloadFile() {
// 检查权限 (同上,省略...)
if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 请求权限
return;
}
Request request = new Request.Builder()
.url(FILE_URL)
.build();
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
// 在主线程显示错误信息
runOnUiThread(() -> {
Toast.makeText(getApplicationContext(), "下载失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
@Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
// 获取响应体
InputStream inputStream = response.body().byteStream();
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File file = new File(directory, FILE_NAME);
FileOutputStream outputStream = new FileOutputStream(file);
byte[] buffer = new new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
// 在主线程显示成功信息
runOnUiThread(() -> {
Toast.makeText(getApplicationContext(), "文件下载成功: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
});
}
});
}
}
优点:
- 代码更简洁、易读。
enqueue方法内部已经处理了线程切换,回调在主线程执行。- 功能强大,支持拦截器、缓存等。
使用 Retrofit (RESTful API 的首选)
如果你的服务器提供的是 RESTful API,并且你下载的文件是 API 响应的一部分(通过一个 GET /files/{id} 接口获取),Retrofit 是最佳选择。
适用场景:
- 下载与 API 请求紧密相关的文件。
- 项目中已经大量使用 Retrofit 进行数据交互。
实现步骤:
- 添加依赖:
retrofit和converter-gson(或其他)。 - 定义 API 接口。
- 创建 Retrofit 实例。
- 调用接口方法并处理响应。
代码示例:
添加依赖 (build.gradle):
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0") // 如果你需要解析JSON
}
定义 API 接口和 Service:

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
public interface FileApiService {
@GET("files/{file_id}")
Call<ResponseBody> downloadFile(@Path("file_id") String fileId);
}
执行下载:
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitFileDownloader {
public void downloadFile(String fileId) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://example.com/api/")
.addConverterFactory(GsonConverterFactory.create())
.build();
FileApiService service = retrofit.create(FileApiService.class);
Call<ResponseBody> call = service.downloadFile(fileId);
call.enqueue(new retrofit2.Callback<ResponseBody>() {
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
// 处理失败
}
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
if (response.isSuccessful()) {
ResponseBody body = response.body();
if (body != null) {
saveFile(body, "downloaded_from_api.pdf");
}
} else {
// 处理非 2xx 响应
}
}
});
}
private void saveFile(ResponseBody body, String fileName) {
// 保存文件的逻辑与 OkHttp 中的完全相同
// ...
}
}
优点:
- 与 RESTful API 完美集成,代码结构清晰。
- 类型安全,编译时检查。
- 同样支持异步和同步。
进阶:使用 DownloadManager (系统下载服务)
对于大文件下载,或者希望利用系统自带的下载管理器(如显示在通知栏、支持断点续传、可以被用户管理),可以使用 DownloadManager。
适用场景:
- 下载大文件。
- 希望下载任务在后台持久化,即使应用关闭也能继续。
- 需要标准的下载进度和通知。
实现步骤:
- 获取网络权限。
- 创建
DownloadManager.Request对象,设置下载 URL 和保存路径。 - 获取
DownloadManager系统服务。 - 调用
enqueue()方法开始下载,会返回一个下载ID。 - 通过
DownloadManager.Query查询下载状态。
代码示例:
import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class DownloadManagerActivity extends AppCompatActivity {
private DownloadManager downloadManager;
private long downloadId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... 初始化UI ...
downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
startDownload();
}
private void startDownload() {
Uri uri = Uri.parse("http://example.com/path/to/your/large_file.zip");
DownloadManager.Request request = new DownloadManager.Request(uri);
// 设置允许在移动网络下下载
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
// 设置通知的标题和描述
request.setTitle("大文件下载");
request.setDescription("正在下载一个大文件...");
// 设置保存路径和文件名
request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "large_file.zip");
// 开始下载,获取唯一的下载ID
downloadId = downloadManager.enqueue(request);
}
// 你可以通过 BroadcastReceiver 来监听下载完成事件
// 或者通过 query() 方法主动查询状态
}
总结与对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| HttpURLConnection | 无需依赖,Java标准库 | 代码繁琐,功能有限,线程处理麻烦 | 简单任务,不想引入第三方库 |
| OkHttp | 强烈推荐,高效、简洁、功能强大 | 需要引入第三方库 | 绝大多数网络请求场景,特别是文件下载 |
| Retrofit | 与API完美集成,类型安全,代码优雅 | 主要用于RESTful API,不直接用于普通URL下载 | 下载作为API一部分的项目 |
| DownloadManager | 系统级,支持断点续传、后台持久化、标准通知 | 只能下载HTTP/HTTPS URL,控制粒度较粗 | 大文件下载,需要系统级下载体验 |
给你的建议:
- 如果是新项目或任何非简单的下载需求,直接使用 OkHttp 或 Retrofit,它们是现代 Android 开发的标准。
- 如果是下载非常大的文件,并且希望有系统级别的下载管理体验,使用 DownloadManager。
- 除非有特殊要求,否则尽量避免使用原生且过时的
HttpURLConnection。
