跳转到主内容
思享编程网:思考分享,玩转编程世界!

Android 文件下载(1)

在 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 下载方法,请告诉我!

相关文章