Скачивание файла на 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