¿Cómo puedo solucionar 'android.os.NetworkOnMainThreadException'?
Recibí un error al ejecutar mi proyecto de Android para RssReader.
Código:
URL url = new URL(urlToRssFeed);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
XMLReader xmlreader = parser.getXMLReader();
RssHandler theRSSHandler = new RssHandler();
xmlreader.setContentHandler(theRSSHandler);
InputSource is = new InputSource(url.openStream());
xmlreader.parse(is);
return theRSSHandler.getFeed();
Y muestra el siguiente error:
android.os.NetworkOnMainThreadException
¿Cómo puedo solucionar este problema?
NOTA: AsyncTask quedó obsoleto en el nivel de API 30.
AsyncTask | Desarrolladores de Android
Esta excepción se produce cuando una aplicación intenta realizar una operación de red en su hilo principal. Ejecute su código en AsyncTask
:
class RetrieveFeedTask extends AsyncTask<String, Void, RSSFeed> {
private Exception exception;
protected RSSFeed doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
XMLReader xmlreader = parser.getXMLReader();
RssHandler theRSSHandler = new RssHandler();
xmlreader.setContentHandler(theRSSHandler);
InputSource is = new InputSource(url.openStream());
xmlreader.parse(is);
return theRSSHandler.getFeed();
} catch (Exception e) {
this.exception = e;
return null;
} finally {
is.close();
}
}
protected void onPostExecute(RSSFeed feed) {
// TODO: check this.exception
// TODO: do something with the feed
}
}
Cómo ejecutar la tarea:
En MainActivity.java
el archivo puedes agregar esta línea dentro de tu oncreate()
método.
new RetrieveFeedTask().execute(urlToRssFeed);
No olvides agregar esto al AndroidManifest.xml
archivo:
<uses-permission android:name="android.permission.INTERNET"/>
Casi siempre debes ejecutar operaciones de red en un subproceso o como una tarea asincrónica.
Pero es posible eliminar esta restricción y anular el comportamiento predeterminado, si está dispuesto a aceptar las consecuencias.
Agregar:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
En tu clase,
y
Agregue este permiso en el archivo manifest.xml de Android :
<uses-permission android:name="android.permission.INTERNET"/>
Consecuencias:
Su aplicación (en áreas de conexión a Internet irregular) dejará de responder y se bloqueará, el usuario percibirá lentitud y tendrá que realizar una eliminación forzada, y usted corre el riesgo de que el administrador de actividad elimine su aplicación y le diga al usuario que se ha detenido.
Android tiene algunos buenos consejos sobre buenas prácticas de programación para diseñar con capacidad de respuesta: NetworkOnMainThreadException | Desarrolladores de Android
Resolví este problema usando un nuevo Thread
.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
// Your code goes here
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
La respuesta aceptada tiene algunas desventajas importantes. No es recomendable utilizar AsyncTask para redes a menos que realmente sepas lo que estás haciendo. Algunas de las desventajas incluyen:
- Las AsyncTask creadas como clases internas no estáticas tienen una referencia implícita al objeto Actividad adjunto, su contexto y toda la jerarquía de Vistas creada por esa actividad. Esta referencia evita que la actividad se recopile como basura hasta que se complete el trabajo en segundo plano de AsyncTask. Si la conexión del usuario es lenta y/o la descarga es grande, estas pérdidas de memoria a corto plazo pueden convertirse en un problema; por ejemplo, si la orientación cambia varias veces (y no cancela las tareas en ejecución), o el usuario navega fuera de la Actividad.
- AsyncTask tiene diferentes características de ejecución según la plataforma en la que se ejecuta: antes del nivel API 4, AsyncTasks se ejecuta en serie en un único subproceso en segundo plano; desde el nivel de API 4 hasta el nivel de API 10, AsyncTasks se ejecuta en un grupo de hasta 128 subprocesos; desde el nivel API 11 en adelante, AsyncTask se ejecuta en serie en un único subproceso en segundo plano (a menos que utilice el
executeOnExecutor
método sobrecargado y proporcione un ejecutor alternativo). El código que funciona bien cuando se ejecuta en serie en ICS puede fallar cuando se ejecuta simultáneamente en Gingerbread, por ejemplo, si tiene dependencias inadvertidas en el orden de ejecución.
Si desea evitar pérdidas de memoria a corto plazo, tener características de ejecución bien definidas en todas las plataformas y tener una base para construir un manejo de red realmente sólido, es posible que desee considerar:
- Usar una biblioteca que haga un buen trabajo para usted; hay una buena comparación de bibliotecas de redes en esta pregunta , o
- Usando a
Service
oIntentService
en su lugar, tal vez con aPendingIntent
para devolver el resultado a través del método de ActividadonActivityResult
.
Enfoque de servicio de intención
Desventajas:
- Más código y complejidad que
AsyncTask
, aunque no tanto como podría pensar - Pondrá en cola las solicitudes y las ejecutará en un único hilo en segundo plano. Puede controlar esto fácilmente reemplazándolo
IntentService
con una implementación equivalenteService
, tal vez como esta . - Um, no puedo pensar en ningún otro en este momento en realidad.
Ventajas:
- Evita el problema de pérdida de memoria a corto plazo.
- Si su actividad se reinicia mientras las operaciones de red están en curso, aún puede recibir el resultado de la descarga a través de su
onActivityResult
método . - Una plataforma mejor que AsyncTask para crear y reutilizar código de red sólido. Ejemplo: si necesita realizar una carga importante, puede hacerlo desde
AsyncTask
un archivoActivity
, pero si el usuario sale de la aplicación para atender una llamada telefónica, el sistema puede cerrar la aplicación antes de que se complete la carga. Es menos probable que cierre una aplicación con un archivoService
. - Si usa su propia versión concurrente
IntentService
(como la que vinculé anteriormente), puede controlar el nivel de concurrencia a través del archivoExecutor
.
Resumen de implementación
Puede implementar una función IntentService
para realizar descargas en un único hilo en segundo plano con bastante facilidad.
Paso 1: Crea un IntentService
para realizar la descarga. Puede decirle qué descargar a través de Intent
extras y pasarle un PendingIntent
uso para devolver el resultado a Activity
:
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Intent;
import android.util.Log;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
public class DownloadIntentService extends IntentService {
private static final String TAG = DownloadIntentService.class.getSimpleName();
public static final String PENDING_RESULT_EXTRA = "pending_result";
public static final String URL_EXTRA = "url";
public static final String RSS_RESULT_EXTRA = "url";
public static final int RESULT_CODE = 0;
public static final int INVALID_URL_CODE = 1;
public static final int ERROR_CODE = 2;
private IllustrativeRSSParser parser;
public DownloadIntentService() {
super(TAG);
// make one and reuse, in the case where more than one intent is queued
parser = new IllustrativeRSSParser();
}
@Override
protected void onHandleIntent(Intent intent) {
PendingIntent reply = intent.getParcelableExtra(PENDING_RESULT_EXTRA);
InputStream in = null;
try {
try {
URL url = new URL(intent.getStringExtra(URL_EXTRA));
IllustrativeRSS rss = parser.parse(in = url.openStream());
Intent result = new Intent();
result.putExtra(RSS_RESULT_EXTRA, rss);
reply.send(this, RESULT_CODE, result);
} catch (MalformedURLException exc) {
reply.send(INVALID_URL_CODE);
} catch (Exception exc) {
// could do better by treating the different sax/xml exceptions individually
reply.send(ERROR_CODE);
}
} catch (PendingIntent.CanceledException exc) {
Log.i(TAG, "reply cancelled", exc);
}
}
}
Paso 2: Registre el servicio en el manifiesto:
<service
android:name=".DownloadIntentService"
android:exported="false"/>
Paso 3: Invocar el servicio desde la Actividad, pasando un objeto PendingResult que el Servicio utilizará para devolver el resultado:
PendingIntent pendingResult = createPendingResult(
RSS_DOWNLOAD_REQUEST_CODE, new Intent(), 0);
Intent intent = new Intent(getApplicationContext(), DownloadIntentService.class);
intent.putExtra(DownloadIntentService.URL_EXTRA, URL);
intent.putExtra(DownloadIntentService.PENDING_RESULT_EXTRA, pendingResult);
startService(intent);
Paso 4: Manejar el resultado en onActivityResult:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RSS_DOWNLOAD_REQUEST_CODE) {
switch (resultCode) {
case DownloadIntentService.INVALID_URL_CODE:
handleInvalidURL();
break;
case DownloadIntentService.ERROR_CODE:
handleError(data);
break;
case DownloadIntentService.RESULT_CODE:
handleRSS(data);
break;
}
handleRSS(data);
}
super.onActivityResult(requestCode, resultCode, data);
}
Un proyecto de GitHub que contiene un proyecto completo de Android Studio/ Gradle está disponible aquí .