在移动应用开发中,文件下载和上传功能是常见需求之一。无论是下载应用内的资源文件还是上传用户生成的内容,这两项功能都是必不可少的。在本篇博客中,我们将详细介绍如何在Android应用中实现文件下载和上传功能,以及如何在过程中实现进度跟踪。
引言
在许多应用中,我们经常需要从网络上下载文件,例如图片、音频、视频或其他资源文件。同样,应用也可能需要允许用户上传文件,比如照片、文档等。为了实现这些功能,我们需要考虑到以下几个方面:
权限管理:确保应用有足够的权限来执行文件操作。
下载功能:从网络获取文件并保存到设备中。
上传功能:将设备上的文件上传到服务器。
进度跟踪:在下载和上传过程中,实时更新进度以提供用户反馈。
在本文中,我们将使用Java语言和Android开发环境来实现文件下载和上传功能,并且在这个过程中展示如何实现进度跟踪。
引入权限
<uses-permission android:name="android.permission.INTERNET" /> <!-- 允许应用程序访问互联网,用于进行网络通信。-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 允许应用程序写入外部存储,如 SD 卡。-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 允许应用程序读取外部存储的文件。-->
实现文件下载功能
我们首先来看如何实现文件下载功能。在Android中,我们可以使用OkHttp库来处理网络请求,并使用相关的权限来保存文件到设备中。
// 在 FileDownloader 类中实现 downloadFile 方法
public static void downloadFile(Activity activity, Context context, String fileUrl, String fileName, String dvrOTA, DownloadCallback callback) {
// 检查应用是否具有写入外部存储的权限
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_STORAGE_PERMISSION);
return;
}
// 获取文件保存路径
String externalStorageDir = FileUtil.filePathDir();
List<String> usbPath = USBChecker.getUsbPath();
if(usbPath!=null && usbPath.size() > 0){
externalStorageDir = usbPath.get(0);
}
// 如果外部存储路径不可用,则回调失败信息
if (externalStorageDir == null) {
callback.onFailure("External_storage_not_available");
return;
}
// 构建文件保存目录
String fileDir = externalStorageDir + File.separator + dvrOTA;
File fileDirFile = new File(fileDir);
if (!fileDirFile.exists()) {
boolean created = fileDirFile.mkdirs();
if (!created) {
callback.onFailure("Failed to create file");
return;
}
}
// 构建文件保存路径
String filePath = fileDir + File.separator + fileName;
File targetFile = new File(filePath);
if (!targetFile.exists()) {
try {
boolean created = targetFile.createNewFile();
if (!created) {
callback.onFailure("Failed to create file");
return;
}
} catch (IOException e) {
callback.onFailure("Failed to create file: " + e.getMessage());
return;
}
}
// 构建下载请求
Request request = new Request.Builder().url(fileUrl).build();
// 使用 OkHttp 发起异步请求
NetworkUtils.getOkHttpClientInstance().newCall(request).enqueue(new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful() && response.body() != null) {
long fileSize = Objects.requireNonNull(response.body()).contentLength();
long fileSizeDownloaded = 0;
// 读取响应流并写入文件
try (InputStream inputStream = response.body().byteStream();
OutputStream outputStream = new FileOutputStream(targetFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
fileSizeDownloaded += bytesRead;
int progress = (int) ((fileSizeDownloaded * 100) / fileSize);
callback.onProgress(progress);
}
callback.onSuccess(targetFile);
} catch (IOException e) {
callback.onFailure(e.getMessage());
}
} else {
callback.onFailure("Failed to download file");
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onFailure("Failed to download file: " + e.getMessage());
}
});
}
public class USBChecker {
public static List<String> getUsbPath() {
// 获取外部存储的状态
List<String> usbPath = new ArrayList<>();
File strongDir = new File("/storage");
File[] files = strongDir.listFiles();
if(files!=null){
for (File file: files) {
if(file.isDirectory() && file.canRead() && isUsbDevice(file)){
usbPath.add(file.getAbsolutePath());
}
}
}
return usbPath;
}
public static boolean isUsbDevice(File directory){
if(directory.getName().contains("sd") || directory.getName().contains("udisk")){
return true;
}
return false;
}
}
public interface DownloadCallback {
void onProgress(int progress);
void onSuccess(File file);
void onFailure(String errorMessage);
}
public class FileUtil {
public static String filePathDir(){
if(Build.VERSION.SDK_INT > 28){
return MyApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath();
}else {
return Environment.getExternalStorageDirectory().getPath();
}
}
}
在这段代码中,我们首先检查应用是否具有写入外部存储的权限。如果没有权限,我们会向用户请求权限。接下来,我们构建文件保存路径,并创建必要的目录。然后,我们构建下载请求并使用OkHttp库来执行异步请求。在响应中,我们读取响应流并将其写入文件中,同时更新下载进度。最后,我们通过回调函数通知下载结果。
实现文件上传功能
接下来,让我们来看如何实现文件上传功能。在这里,我们将使用OkHttp库来执行文件上传,并在上传过程中实现进度跟踪。
public static void uploadFile(String fileName, DownloadCallback callback) {
File file = new File(fileName);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(),
RequestBody.create(MediaType.parse("application/octet-stream"), file))
.build();
// 使用自定义的进度RequestBody
ProgressRequestBody progressRequestBody = new ProgressRequestBody(requestBody, new ProgressListener() {
@Override
public void onProgressChanged(long bytesWritten, long totalBytes) {
// 计算并通知进度
int progress = (int) (100 * bytesWritten / totalBytes);
if(progress < 100){
callback.onProgress(progress);
}
if(progress == 100){
callback.onSuccess(file);
}
}
});
Request request = new Request.Builder()
.url("http://"+ Config.DVR_IP +"/action/upload")
.post(progressRequestBody)
.build();
NetworkUtils.getOkHttpClientInstance().newCall(request).enqueue(new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful() && response.body() != null) {
callback.onSuccess(file);
} else {
callback.onFailure("Failed to upload file");
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onFailure("Failed to upload file: " + e.getMessage());
}
});
}
// 定义上传进度监听器接口
interface ProgressListener {
void onProgressChanged(long bytesWritten, long totalBytes);
}
// 自定义RequestBody来包装实际的RequestBody以监听上传进度
static class ProgressRequestBody extends RequestBody {
private final RequestBody requestBody;
private final ProgressListener progressListener;
ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) {
this.requestBody = requestBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return requestBody.contentType();
}
@Override
public long contentLength() throws IOException {
return requestBody.contentLength();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink bufferedSink = Okio.buffer(new ForwardingSink(sink) {
long bytesWritten = 0L;
long totalBytes = 0L;
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
if (totalBytes == 0) {
totalBytes = contentLength();
}
bytesWritten += byteCount;
progressListener.onProgressChanged(bytesWritten, totalBytes);
}
});
requestBody.writeTo(bufferedSink);
bufferedSink.flush();
}
}
在这段代码中,我们首先构建上传文件的请求体,并使用自定义的进度RequestBody来包装实际的RequestBody以监听上传进度。然后,我们构建上传请求并使用OkHttp库来执行异步上传请求。在响应中,我们通过回调函数通知上传结果。
实现文件下载及上传功能工具类
FileDownloader
public class FileDownloader {
public interface DownloadCallback {
void onProgress(int progress);
void onSuccess(File file);
void onFailure(String errorMessage);
}
private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
public static void downloadFile(Activity activity, Context context, String fileUrl, String fileName, String dvrOTA, DownloadCallback callback) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_STORAGE_PERMISSION);
return;
}
String externalStorageDir = FileUtil.filePathDir();
List<String> usbPath = USBChecker.getUsbPath();
if(usbPath!=null && usbPath.size() > 0){
externalStorageDir = usbPath.get(0);
}
if (externalStorageDir == null) {
callback.onFailure("External_storage_not_available");
return;
}
String fileDir = externalStorageDir + File.separator + dvrOTA;
File fileDirFile = new File(fileDir);
if (!fileDirFile.exists()) {
boolean created = fileDirFile.mkdirs();
if (!created) {
callback.onFailure("Failed to create file");
return;
}
}
String filePath = fileDir + File.separator + fileName;
File targetFile = new File(filePath);
if (!targetFile.exists()) {
try {
boolean created = targetFile.createNewFile();
if (!created) {
callback.onFailure("Failed to create file");
return;
}
} catch (IOException e) {
callback.onFailure("Failed to create file: " + e.getMessage());
return;
}
}
Request request = new Request.Builder().url(fileUrl).build();
NetworkUtils.getOkHttpClientInstance().newCall(request).enqueue(new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful() && response.body() != null) {
long fileSize = Objects.requireNonNull(response.body()).contentLength();
long fileSizeDownloaded = 0;
try (InputStream inputStream = response.body().byteStream();
OutputStream outputStream = new FileOutputStream(targetFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
fileSizeDownloaded += bytesRead;
int progress = (int) ((fileSizeDownloaded * 100) / fileSize);
Log.d("Download", "Progress: " + progress);
callback.onProgress(progress);
}
callback.onSuccess(targetFile);
} catch (IOException e) {
callback.onFailure(e.getMessage());
}
} else {
callback.onFailure("Failed to download file");
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onFailure("Failed to download file: " + e.getMessage());
}
});
}
public static void uploadFile(String fileName, DownloadCallback callback) {
File file = new File(fileName);
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", file.getName(),
RequestBody.create(MediaType.parse("application/octet-stream"), file))
.build();
// 使用自定义的进度RequestBody
ProgressRequestBody progressRequestBody = new ProgressRequestBody(requestBody, new ProgressListener() {
@Override
public void onProgressChanged(long bytesWritten, long totalBytes) {
// 计算并通知进度
int progress = (int) (100 * bytesWritten / totalBytes);
if(progress < 100){
callback.onProgress(progress);
}
if(progress == 100){
callback.onSuccess(file);
}
}
});
Request request = new Request.Builder()
.url("http://"+ Config.DVR_IP +"/action/upload")
.post(progressRequestBody)
.build();
NetworkUtils.getOkHttpClientInstance().newCall(request).enqueue(new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
if (response.isSuccessful() && response.body() != null) {
callback.onSuccess(file);
} else {
callback.onFailure("Failed to upload file");
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onFailure("Failed to upload file: " + e.getMessage());
}
});
}
// 定义上传进度监听器接口
interface ProgressListener {
void onProgressChanged(long bytesWritten, long totalBytes);
}
// 自定义RequestBody来包装实际的RequestBody以监听上传进度
static class ProgressRequestBody extends RequestBody {
private final RequestBody requestBody;
private final ProgressListener progressListener;
ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) {
this.requestBody = requestBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return requestBody.contentType();
}
@Override
public long contentLength() throws IOException {
return requestBody.contentLength();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
BufferedSink bufferedSink = Okio.buffer(new ForwardingSink(sink) {
long bytesWritten = 0L;
long totalBytes = 0L;
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
if (totalBytes == 0) {
totalBytes = contentLength();
}
bytesWritten += byteCount;
progressListener.onProgressChanged(bytesWritten, totalBytes);
}
});
requestBody.writeTo(bufferedSink);
bufferedSink.flush();
}
}
}
FileUtil
public static String filePathDir(){
if(Build.VERSION.SDK_INT > 28){
return MyApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath();
}else {
return Environment.getExternalStorageDirectory().getPath();
}
}
USBChecker
public class USBChecker {
public static List<String> getUsbPath() {
// 获取外部存储的状态
List<String> usbPath = new ArrayList<>();
File strongDir = new File("/storage");
File[] files = strongDir.listFiles();
if(files!=null){
for (File file: files) {
if(file.isDirectory() && file.canRead() && isUsbDevice(file)){
usbPath.add(file.getAbsolutePath());
}
}
}
return usbPath;
}
public static boolean isUsbDevice(File directory){
if(directory.getName().contains("sd") || directory.getName().contains("udisk")){
return true;
}
return false;
}
}
调用文件下载及上传功能的方法
文件下载
private void downFile() {
FileDownloader.downloadFile(this, getApplicationContext(), fileUrl, fileName, filePath, new FileDownloader.DownloadCallback() {
@Override
public void onProgress(int progress) {
// 更新下载进度
runOnUiThread(new Runnable() {
@Override
public void run() {
progress;
}
});
}
@Override
public void onSuccess(File file) {
// 下载成功,处理文件
Log.e("progress---", "onSuccess");
}
@Override
public void onFailure(String errorMessage) {
// 下载失败,处理错误
Log.e("progress---", "" + errorMessage);
runOnUiThread(new Runnable() {
@Override
public void run() {
if (errorMessage.equals("External_storage_not_available")) {
ToastHelps.showShortTime("下载失败,请检查存储卡");
} else {
ToastHelps.showShortTime("下载失败");
}
}
});
}
});
}
文件上传
private void uploadFile() {
FileDownloader.uploadFile(absolutePath, new FileDownloader.DownloadCallback() {
@Override
public void onProgress(int progress) {
// 更新下载进度
runOnUiThread(new Runnable() {
@Override
public void run() {
progress;
}
});
}
@Override
public void onSuccess(File file) {
// 下载成功,处理文件
Log.e("progress---", "onSuccess");
}
@Override
public void onFailure(String errorMessage) {
// 下载失败,处理错误
Log.e("progress---", "" + errorMessage);
runOnUiThread(new Runnable() {
@Override
public void run() {
if (errorMessage.equals("External_storage_not_available")) {
ToastHelps.showShortTime("升级失败,请检查存储卡");
} else {
ToastHelps.showShortTime("升级失败");
}
}
});
}
});
}
总结
在本文中,我们介绍了如何在Android应用中实现文件下载和上传功能,并实现了进度跟踪功能。通过使用OkHttp库处理网络请求和自定义的进度RequestBody,我们能够轻松地实现这些功能。希望本文能够帮助开发者更好地理解文件操作和网络请求,并能够在实际应用中应用这些技术。