Как исправить ошибку android.os.NetworkOnMainThreadException в Android?
Проблема: почему возникает NetworkOnMainThreadException
В Android начиная с версии 3.0 (Honeycomb) система запрещает выполнять сетевые операции в главном (UI) потоке. Это сделано, чтобы приложение не "зависало" при медленном соединении — пользовательский интерфейс должен оставаться отзывчивым. Если вы делаете HTTP-запрос в onCreate() или любом другом методе, который выполняется в главном потоке, система выбрасывает NetworkOnMainThreadException.
Решение: используем корутины с Retrofit (современный подход)
Самый правильный способ в современной Android-разработке — использовать Kotlin Coroutines в сочетании с библиотекой Retrofit для сетевых запросов. Но так как ваш код на Java, я покажу решение на Java с использованием AsyncTask (хотя он помечен как устаревший, он всё ещё работает и прост для понимания новичками) и более современный вариант с Thread + Handler.
Вариант 1: AsyncTask (простой, но устаревший)
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
new NetworkTask().execute("https://api.example.com/data");
}
private class NetworkTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
// Этот код выполняется в фоновом потоке
try {
URL url = new URL(urls[0]);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onPostExecute(String result) {
// Этот код выполняется в главном потоке после завершения фоновой работы
if (result != null) {
textView.setText(result);
} else {
textView.setText("Ошибка загрузки");
}
}
}
}
Вариант 2: Thread + Handler (более гибкий)
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Handler mainHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
new Thread(new Runnable() {
@Override
public void run() {
// Фоновый поток
try {
URL url = new URL("https://api.example.com/data");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
final String result = response.toString();
// Возвращаем результат в главный поток
mainHandler.post(new Runnable() {
@Override
public void run() {
textView.setText(result);
}
});
} catch (Exception e) {
e.printStackTrace();
mainHandler.post(new Runnable() {
@Override
public void run() {
textView.setText("Ошибка загрузки");
}
});
}
}
}).start();
}
}
Рекомендации
- Не используйте чистый HttpURLConnection в продакшене — лучше взять Retrofit или OkHttp, которые обрабатывают много подводных камней (таймауты, кэширование, переподключение).
- Всегда обрабатывайте ошибки — сеть нестабильна, пользователь может быть без интернета.
- Не забывайте про разрешения — в AndroidManifest.xml нужно добавить
<uses-permission android:name="android.permission.INTERNET" />. - Для долгих операций (загрузка файлов, стриминг) используйте WorkManager или Services.
Помните: главный поток — только для UI. Любая длительная операция (сеть, база данных, сложные вычисления) должна выполняться в фоне.
<<Альтернативный подход: использование библиотеки Volley
Если вы не хотите возиться с низкоуровневыми потоками, рекомендую использовать библиотеку Volley от Google. Она специально создана для простых сетевых запросов в Android и автоматически управляет потоками.
Добавьте зависимость в build.gradle:
implementation 'com.android.volley:volley:1.2.1'
Пример использования:
public class MainActivity extends AppCompatActivity {
private TextView textView;
private RequestQueue requestQueue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
requestQueue = Volley.newRequestQueue(this);
String url = "https://api.example.com/data";
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Успех — выполняется в главном потоке
textView.setText(response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Ошибка
textView.setText("Ошибка: " + error.getMessage());
}
});
requestQueue.add(stringRequest);
}
}
Volley автоматически выполняет запрос в фоновом потоке и возвращает результат в главный. Также он поддерживает кэширование, повторные попытки и отмену запросов при уничтожении Activity. Это отличный выбор для небольших проектов или прототипов.