Утро, которое начинается с 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).