Поймали NetworkOnMainThreadException в 2025? Полный гайд по исправлению на Java

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

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

Начать курс

Утро, которое начинается с NetworkOnMainThreadException

Вы пишете приложение на Android. Всё идёт отлично, пока вы не решаете сделать простой HTTP-запрос в кнопке. Вы нажимаете «Обновить» — и приложение падает с красным стеком. Знакомо? android.os.NetworkOnMainThreadException — это классическая ошибка, которую хотя бы раз в жизни видел каждый Android-разработчик. В 2025 году она всё ещё жива, и новички продолжают наступать на те же грабли.

В этой статье мы разберём, почему возникает NetworkOnMainThreadException, как его исправить на Java, и какие подходы считаются лучшими практиками в 2025 году. Вы получите готовые решения с кодом, которые можно сразу вставить в свой проект.

Почему возникает NetworkOnMainThreadException?

Android — это не просто Java. Это система с жёсткими правилами. Одно из них: не выполняйте сетевые операции в главном (UI) потоке. Главный поток отвечает за отрисовку интерфейса и обработку касаний. Если он заблокируется хотя бы на секунду, пользователь увидит «тормоза», а если на пять секунд — система покажет диалог «Приложение не отвечает» (ANR).

Начиная с Android 3.0 (Honeycomb), Google добавила защиту: любой сетевой вызов в главном потоке выбрасывает android.os.NetworkOnMainThreadException. Исключение говорит: «Эй, ты делаешь что-то опасное для UX!».

В 2025 году это правило никуда не делось. Даже если вы используете современные библиотеки (Retrofit, OkHttp), вы всё равно обязаны запускать их в фоновом потоке. Исключение — это не баг, а фича безопасности.

Топ-5 способов исправить ошибку (Java, 2025)

Мы рассмотрим пять подходов — от устаревших до современных. Выбирайте тот, который подходит под ваш проект.

1. Классический AsyncTask (легаси, но рабочий)

AsyncTask был стандартом лет 10 назад. В 2025 году его использовать не рекомендуют (он deprecated в API 30+), но на старых проектах вы всё ещё можете его встретить.

private class MyNetworkTask extends AsyncTask<String, Void, String> {
    @Override
    protected String doInBackground(String... params) {
        // Это уже фоновый поток
        try {
            URL url = new URL(params[0]);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line);
            }
            return result.toString();
        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }

    @Override
    protected void onPostExecute(String result) {
        // Это уже UI поток
        textView.setText(result);
    }
}

// Запуск:
new MyNetworkTask().execute("https://api.example.com/data");

Минусы: AsyncTask трудно тестировать, он неудобен для цепочек запросов, а в случае поворота экрана может утечь память. Используйте только если проект написан на Java 7-8.

2. Java Thread + Handler (чистый подход)

Самый простой способ — создать отдельный поток и вернуть результат в UI через Handler.

private Handler mainHandler = new Handler(Looper.getMainLooper());

private void fetchDataInBackground() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            // Фоновый поток
            final String result = performNetworkCall();
            
            // Возвращаемся в UI поток
            mainHandler.post(new Runnable() {
                @Override
                public void run() {
                    textView.setText(result);
                }
            });
        }
    }).start();
}

private String performNetworkCall() {
    // Ваша сетевая логика
    return "Данные получены";
}

Этот метод прост и понятен. Но у него есть недостаток: если поток упадёт с ошибкой, вы не получите нормальной обработки исключений. Ручное управление потоками — это боль.

3. ExecutorService (рекомендуемый для Java 8+)

Если вы используете Java 8 или выше, ExecutorService — ваш выбор. Это пул потоков, который переиспользует ресурсы и даёт больше контроля.

private ExecutorService executor = Executors.newSingleThreadExecutor();
private Handler mainHandler = new Handler(Looper.getMainLooper());

private void fetchDataWithExecutor() {
    executor.execute(new Runnable() {
        @Override
        public void run() {
            String result = performNetworkCall();
            mainHandler.post(new Runnable() {
                @Override
                public void run() {
                    textView.setText(result);
                }
            });
        }
    });
}

// Не забудьте закрыть executor в onDestroy():
@Override
protected void onDestroy() {
    super.onDestroy();
    executor.shutdown();
}

Вы также можете использовать CompletableFuture для более элегантного кода:

CompletableFuture.supplyAsync(() -> performNetworkCall())
        .thenAccept(result -> runOnUiThread(() -> textView.setText(result)));

Плюсы: легко читать, легко тестировать, нет утечек памяти при правильном shutdown.

4. RxJava 3 (реактивный подход)

RxJava — мощный инструмент, который жив и в 2025 году. Он отлично работает с сетевыми запросами и автоматически переключает потоки.

import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;

private void fetchDataWithRxJava() {
    Single.fromCallable(() -> performNetworkCall())
            .subscribeOn(Schedulers.io())   // Фоновый поток
            .observeOn(AndroidSchedulers.mainThread()) // UI поток
            .subscribe(
                    result -> textView.setText(result),
                    error -> textView.setText("Ошибка: " + error.getMessage())
            );
}

RxJava требует подключения зависимости (io.reactivex.rxjava3:rxjava:3.1.8 и io.reactivex.rxjava3:rxandroid:3.0.2). Но он даёт мощные операторы для трансформации данных и обработки ошибок.

5. Kotlin Coroutines (если вы всё-таки используете Kotlin)

Хотя статья про Java, в 2025 году многие проекты мигрируют на Kotlin. Coroutines — это современный стандарт. Но даже в Java-проекте вы можете использовать Coroutines для Java (через kotlinx-coroutines-core), хотя это не очень удобно.

Если ваш проект на Java, лучше остановиться на ExecutorService или RxJava. Если вы готовы к Kotlin — Coroutines решают проблему в две строки:

// Kotlin
lifecycleScope.launch(Dispatchers.IO) {
    val result = performNetworkCall()
    withContext(Dispatchers.Main) {
        textView.text = result
    }
}

Какой способ выбрать в 2025?

Вот краткая таблица для принятия решения:

  • Старый проект на Java 7 → AsyncTask (или лучше Thread + Handler).
  • Современный проект на Java 8+ → ExecutorService + Handler или CompletableFuture.
  • Проект с частыми сетевыми запросами → RxJava (Schedulers.io).

Блоги

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