Avatar ·

Как скачать файл в Android с отображением прогресса в ProgressDialog?

Всем привет! Я новичок в Android-разработке и пытаюсь реализовать загрузку файла из интернета с отображением прогресса в диалоговом окне. У меня есть простой код на Java, который использует AsyncTask, но прогресс не обновляется, а диалог закрывается сразу после начала загрузки. Я перепробовал кучу вариантов из интернета, но ничего не работает как надо. Помогите разобраться, пожалуйста! Вот мой текущий код: ```java public class DownloadFile extends AsyncTask { private ProgressDialog progressDialog; private Context context; public DownloadFile(Context context) { this.context = context; progressDialog = new ProgressDialog(context); } @Override protected void onPreExecute() { progressDialog.setMessage("Загрузка..."); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setCancelable(false); progressDialog.show(); } @Override protected String doInBackground(String... params) { try { URL url = new URL(params[0]); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.connect(); int fileLength = connection.getContentLength(); InputStream input = new BufferedInputStream(url.openStream()); OutputStream output = new FileOutputStream("/sdcard/downloadedfile.txt"); byte data[] = new byte[1024]; long total = 0; int count; while ((count = input.read(data)) != -1) { total += count; publishProgress((int) (total * 100 / fileLength)); output.write(data, 0, count); } output.flush(); output.close(); input.close(); } catch (Exception e) { e.printStackTrace(); } return "Загрузка завершена"; } @Override protected void onProgressUpdate(Integer... values) { progressDialog.setProgress(values[0]); } @Override protected void onPostExecute(String result) { progressDialog.dismiss(); Toast.makeText(context, result, Toast.LENGTH_LONG).show(); } } ``` Проблема в том, что прогресс-бар показывает только 0% и 100% в конце, а не плавное обновление. Что я делаю не так? Может быть, проблема в том, что я вызываю это из главного потока? Заранее спасибо за помощь! <<>>

Решение проблемы: корректная реализация загрузки файла с прогрессом в 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
  • Не