Una mañana que comienza con NetworkOnMainThreadException
\n\nEstás escribiendo una aplicación en Android. Todo va perfectamente hasta que decides hacer una simple solicitud HTTP en un botón. Presionas "Actualizar" — y la aplicación se cae con un stack rojo. ¿Te suena familiar? android.os.NetworkOnMainThreadException es el error clásico que todo desarrollador Android ha visto al menos una vez en la vida. En 2025, sigue vigente, y los principiantes continúan cayendo en la misma trampa.
\n\nEn este artículo, analizaremos por qué ocurre NetworkOnMainThreadException, cómo solucionarlo en Java, y qué enfoques se consideran las mejores prácticas en 2025. Obtendrás soluciones listas con código que puedes insertar directamente en tu proyecto.
\n\n¿Por qué ocurre NetworkOnMainThreadException?
\n\nAndroid no es solo Java. Es un sistema con reglas estrictas. Una de ellas: no realices operaciones de red en el hilo principal (UI). El hilo principal se encarga de dibujar la interfaz y procesar los toques. Si se bloquea aunque sea por un segundo, el usuario verá "lag", y si se bloquea por cinco segundos, el sistema mostrará el diálogo "La aplicación no responde" (ANR).
\n\nA partir de Android 3.0 (Honeycomb), Google añadió una protección: cualquier llamada de red en el hilo principal lanza android.os.NetworkOnMainThreadException. La excepción dice: "¡Oye, estás haciendo algo peligroso para la experiencia de usuario!".
En 2025, esta regla no ha desaparecido. Incluso si usas bibliotecas modernas (Retrofit, OkHttp), igual estás obligado a ejecutarlas en un hilo en segundo plano. La excepción no es un error, sino una característica de seguridad.
\n\nTop 5 formas de solucionar el error (Java, 2025)
\n\nVeremos cinco enfoques, desde los obsoletos hasta los modernos. Elige el que se adapte a tu proyecto.
\n\n1. AsyncTask clásico (legado, pero funcional)
\n\nAsyncTask fue el estándar hace unos 10 años. En 2025 no se recomienda su uso (está deprecado en API 30+), pero en proyectos antiguos aún puedes encontrarlo.
\n\nprivate class MyNetworkTask extends AsyncTask<String, Void, String> {\n @Override\n protected String doInBackground(String... params) {\n // Este ya es un hilo en segundo plano\n try {\n URL url = new URL(params[0]);\n HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));\n StringBuilder result = new StringBuilder();\n String line;\n while ((line = reader.readLine()) != null) {\n result.append(line);\n }\n return result.toString();\n } catch (Exception e) {\n return "Error: " + e.getMessage();\n }\n }\n\n @Override\n protected void onPostExecute(String result) {\n // Este ya es el hilo UI\n textView.setText(result);\n }\n}\n\n// Ejecución:\nnew MyNetworkTask().execute("https://api.example.com/data");\n\nDesventajas: AsyncTask es difícil de probar, no es conveniente para cadenas de solicitudes y, en caso de rotación de pantalla, puede provocar fugas de memoria. Úsalo solo si el proyecto está escrito en Java 7-8.
\n\n2. Java Thread + Handler (enfoque puro)
\n\nLa forma más sencilla es crear un hilo separado y devolver el resultado a la UI a través de Handler.
\n\nprivate Handler mainHandler = new Handler(Looper.getMainLooper());\n\nprivate void fetchDataInBackground() {\n new Thread(new Runnable() {\n @Override\n public void run() {\n // Hilo en segundo plano\n final String result = performNetworkCall();\n \n // Regresamos al hilo UI\n mainHandler.post(new Runnable() {\n @Override\n public void run() {\n textView.setText(result);\n }\n });\n }\n }).start();\n}\n\nprivate String performN