Как скачать файл в Android с отображением прогресса в ProgressDialog?
Решение проблемы: корректная реализация загрузки файла с прогрессом в Android
Ваш код близок к правильному, но есть несколько критических ошибок, которые мешают корректному обновлению прогресса. Вот исправленная версия с подробными объяснениями.
Основные проблемы в вашем коде:
- Сетевые операции в основном потоке — вы используете
url.openStream()вместоconnection.getInputStream(), что может вызвать блокировку - Отсутствие обработки ошибок — при сбоях загрузки диалог не закрывается
- Некорректный расчет прогресса —
fileLengthможет быть -1, если сервер не передает размер файла - Устаревший подход — AsyncTask в современных версиях Android работает нестабильно, лучше использовать более новые решения
Исправленный код с использованием AsyncTask:
public class DownloadFileTask extends AsyncTask {
private ProgressDialog progressDialog;
private Context context;
public DownloadFileTask(Context context) {
this.context = context;
progressDialog = new ProgressDialog(context);
progressDialog.setMessage("Загрузка...");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setCancelable(false);
}
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected String doInBackground(String... params) {
HttpURLConnection connection = null;
InputStream input = null;
OutputStream output = null;
try {
URL url = new URL(params[0]);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(15000);
connection.setReadTimeout(15000);
connection.connect();
// Проверка кода ответа
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return "Ошибка сервера: " + connection.getResponseCode();
}
int fileLength = connection.getContentLength();
if (fileLength == -1) {
return "Размер файла не определен";
}
input = new BufferedInputStream(connection.getInputStream());
output = new FileOutputStream(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS) + "/downloadedfile.txt"
);
byte[] buffer = new byte[4096]; // Увеличили буфер для производительности
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
totalBytesRead += bytesRead;
output.write(buffer, 0, bytesRead);
// Расчет и публикация прогресса
int progress = (int) (totalBytesRead * 100 / fileLength);
publishProgress(progress);
}
output.flush();
return "Загрузка завершена";
} catch (IOException e) {
e.printStackTrace();
return "Ошибка: " + e.getMessage();
} finally {
try {
if (input != null) input.close();
if (output != null) output.close();
if (connection != null) connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
protected void onProgressUpdate(Integer... values) {
if (progressDialog.isShowing()) {
progressDialog.setProgress(values[0]);
progressDialog.setMessage("Загрузка: " + values[0] + "%");
}
}
@Override
protected void onPostExecute(String result) {
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
Toast.makeText(context, result, Toast.LENGTH_LONG).show();
}
}
Важные улучшения:
- Использование connection.getInputStream() вместо url.openStream()
- Проверка кода ответа HTTP — 200 OK
- Обработка случая fileLength = -1
- Закрытие ресурсов в finally — гарантировано даже при ошибках
- Использование Environment.DIRECTORY_DOWNLOADS — правильный путь для загрузок
Современный подход: использование DownloadManager
Начиная с Android 9 (API 28), AsyncTask считается устаревшим. Рекомендуется использовать DownloadManager:
public void downloadFileWithManager(Context context, String url, String fileName) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle("Загрузка файла");
request.setDescription("Идет загрузка...");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
downloadManager.enqueue(request);
}
Это решение автоматически обрабатывает прогресс, уведомления и возобновление при обрыве связи.
<<Альтернативное решение: использование библиотеки OkHttp с ProgressListener
Если вам нужен более гибкий контроль над загрузкой, рекомендую использовать OkHttp — это современная и надежная библиотека для работы с HTTP.
Сначала добавьте зависимость в build.gradle:
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
Вот пример реализации с кастомным ResponseBody для отслеживания прогресса:
public class DownloadHelper {
public static void downloadFile(String url, ProgressCallback callback) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onError(e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
callback.onError("Ошибка: " + response.code());
return;
}
long contentLength = response.body().contentLength();
InputStream inputStream = response.body().byteStream();
OutputStream outputStream = new FileOutputStream(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS) + "/file.zip"
);
byte[] buffer = new byte[4096];
long totalBytesRead = 0;
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
totalBytesRead += bytesRead;
outputStream.write(buffer, 0, bytesRead);
int progress = (int) (totalBytesRead * 100 / contentLength);
callback.onProgress(progress);
}
outputStream.close();
inputStream.close();
callback.onSuccess("Загрузка завершена");
}
});
}
public interface ProgressCallback {
void onProgress(int percent);
void onSuccess(String message);
void onError(String error);
}
}
Преимущества OkHttp:
- Асинхронная работа по умолчанию (не блокирует UI)
- Поддержка HTTP/2 и автоматическое сжатие
- Легко интегрируется с архитектурой MVVM и LiveData
- Не
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Обработка ошибок в C#: try/catch, finally и лучшие практики