Скачиваем файл на Android с индикатором прогресса: полный гайд

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Скачивание файла на Android: от идеи до работающего кода

Каждый Android-разработчик рано или поздно сталкивается с задачей загрузки файла из сети. Это может быть обновление приложения, скачивание изображения, PDF-документа или музыкального трека. Но просто скачать файл — половина дела. Пользователь должен видеть, что процесс идёт: сколько скачано, сколько осталось, и что приложение не зависло.

В этой статье мы разберём классический вопрос с Stack Overflow, который набрал тысячи просмотров: как скачать файл на Android и показать прогресс в диалоговом окне. Мы напишем код на Java, используя AsyncTask и ProgressDialog (хотя в современных проектах их часто заменяют корутинами или RxJava, но понимание классики — база, которая никуда не делась).

Готовы? Поехали!

Почему так сложно? Три подводных камня загрузки в Android

На первый взгляд, задача простая: открыть соединение, прочитать поток байтов, записать в файл. Но на практике разработчики спотыкаются о три вещи:

  • UI-поток блокируется — если запустить загрузку в главном потоке, приложение «замрёт» и Android покажет диалог «Приложение не отвечает».
  • Прогресс не обновляется — даже если вы считаете байты, вы не можете просто так менять UI из фонового потока.
  • Конфигурация меняется — пользователь повернул экран, и ваш Activity пересоздался. Диалог пропал, а загрузка идёт в никуда.

Мы решим все три проблемы. Начнём с архитектуры, а потом напишем код.

Выбираем инструменты: AsyncTask и ProgressDialog

Для образовательных целей используем классический подход:

  • AsyncTask — для выполнения фоновой работы и публикации прогресса.
  • ProgressDialog — для отображения процентов.
  • HttpURLConnection — для HTTP-запроса (он всё ещё жив, хоть и устарел).

Важно: В реальных проектах сегодня чаще используют DownloadManager, WorkManager или библиотеки вроде Retrofit + OkHttp. Но понимание того, как работает загрузка «руками» — это must-have для любого разработчика.

Полный код: скачивание файла с ProgressDialog

Ниже представлен готовый класс для загрузки. Код прокомментирован на русском, чтобы вы сразу понимали, что происходит.

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Environment;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class DownloadFileTask extends AsyncTask<String, Integer, String> {

    private Context context;
    private ProgressDialog progressDialog;

    public DownloadFileTask(Context context) {
        this.context = context;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // Создаём и показываем диалог прогресса
        progressDialog = new ProgressDialog(context);
        progressDialog.setTitle("Загрузка файла");
        progressDialog.setMessage("Идёт скачивание...");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.setCancelable(false); // Не даём закрыть случайно
        progressDialog.show();
    }

    @Override
    protected String doInBackground(String... params) {
        String fileUrl = params[0];
        String fileName = "downloaded_file.pdf"; // Можно передавать вторым параметром
        InputStream inputStream = null;
        FileOutputStream outputStream = null;

        try {
            // 1. Устанавливаем соединение
            URL url = new URL(fileUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();

            // 2. Проверяем код ответа
            int responseCode = connection.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                return "Ошибка сервера: " + responseCode;
            }

            // 3. Получаем длину файла для расчёта процентов
            int fileLength = connection.getContentLength();
            progressDialog.setMax(fileLength); // Максимум для ProgressBar

            // 4. Читаем данные и пишем в файл
            inputStream = connection.getInputStream();
            File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
            File outputFile = new File(storageDir, fileName);
            outputStream = new FileOutputStream(outputFile);

            byte[] buffer = new byte[4096];
            int bytesRead;
            int totalBytesRead = 0;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
                totalBytesRead += bytesRead;

                // 5. Публикуем прогресс (вызовет onProgressUpdate)
                publishProgress(totalBytesRead);
            }

            return "Файл сохранён: " + outputFile.getAbsolutePath();

        } catch (Exception e) {
            return "Ошибка: " + e.getMessage();
        } finally {
            try {
                if (inputStream != null) inputStream.close();
                if (outputStream != null) outputStream.close();
            } catch (Exception ignored) {}
        }
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        // Обновляем прогресс в UI-потоке
        int currentProgress = values[0];
        progressDialog.setProgress(currentProgress);
        // Показываем проценты в заголовке
        int percent = (int) ((double) currentProgress / progressDialog.getMax() * 100);
        progressDialog.setTitle("Загрузка: " + percent + "%");
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
        // Закрываем диалог
        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
        // Показываем результат
        Toast.makeText(context, result, Toast.LENGTH_LONG).show();
    }
}

Как запустить этот код?

В вашей Activity (например, MainActivity.java) просто вызовите:

new DownloadFileTask(this).execute("https://example.com/sample.pdf");

Не забудьте добавить разрешение в AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

И запросить разрешения на запись в рантайме, если ваше приложение нацелено на Android 6.0+ (API 23+).

Разбор ключевых моментов: что здесь важно?

1. Потоки и UI

doInBackground работает в фоновом потоке — здесь мы читаем и пишем данные. onProgressUpdate и onPostExecute выполняются в главном потоке, поэтому мы можем безопасно обновлять ProgressDialog и показывать Toast.

2. Расчёт прогресса

Мы используем connection.getContentLength() чтобы узнать точный размер файла. Затем в цикле считаем прочитанные байты и вызываем publishProgress(totalBytesRead). Это даёт нам плавный и точный прогресс.

3. Обработка ошибок

В коде есть проверка кода ответа сервера. Если сервер вернул 404

Блоги

Рекомендаций по книгам