Comment réparer 'android.os.NetworkOnMainThreadException'?

2396

J'ai reçu une erreur lors de l'exécution de mon projet Android pour RssReader.

Code:

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();

Et il montre l'erreur ci-dessous:

android.os.NetworkOnMainThreadException

Comment puis-je résoudre ce problème?

bejoy george
la source
131
Lisez cet article de blog sur NetworkOnMainThreadException pour plus d'informations. Il explique pourquoi cela se produit sur Android 3.0 et supérieur.
Adrian Monk
6
Pour être sur la bonne voie, lisez d'abord les demandes de réseau dans Android, puis je recommanderais d'étudier "Volley".
Anuj Sharma
3
Il existe de nombreuses bibliothèques alternatives qui résolvent ce problème. Beaucoup sont répertoriés au bas de cette page . Si vous en avez plus, nous les prenons :)
Snicolas
Vous devez exécuter des activités Internet sur un thread distinct du thread principal (UI)
Naveed Ahmad
«En raison d'un bogue dans les versions précédentes d'Android, le système n'a pas signalé l'écriture sur un socket TCP sur le thread principal comme une violation en mode strict. Android 7.0 corrige ce bogue. Les applications qui présentent ce comportement lancent désormais un android.os. NetworkOnMainThreadException. " - Donc, certains d'entre nous n'ont pas touché ça jusqu'à récemment! developer.android.com/about/versions/nougat/…
Jay

Réponses:

2548

Cette exception est levée lorsqu'une application tente d'effectuer une opération de mise en réseau sur son thread principal. Exécutez votre code dans 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
    }
}

Comment exécuter la tâche:

Dans le MainActivity.javafichier, vous pouvez ajouter cette ligne dans votre oncreate()méthode

new RetrieveFeedTask().execute(urlToRssFeed);

N'oubliez pas d'ajouter ceci au AndroidManifest.xmlfichier:

<uses-permission android:name="android.permission.INTERNET"/>
Michael Spector
la source
37
Je pense qu'il convient de noter ici que l'extrait de code ci-dessus est censé être une sous-classe (classe interne), de préférence privée. De cette façon, lorsque la tâche AsyncTask se termine, vous pouvez toujours manipuler les entrailles de votre classe.
dyslexicanaboko
4
En fait, j'ai fait la même chose que celle mentionnée ci-dessus, mais je suis confronté à cette erreur java.lang.RuntimeException: impossible de créer un gestionnaire à l'intérieur du thread qui n'a pas appelé Looper.prepare ()
Dhruv Tyagi
68
C'est exactement la mauvaise réponse. Je rencontre cela tout le temps dans le code des peuples, et c'est ennuyeux de devoir le réparer tout le temps. AsyncTask ne doit pas être utilisé pour l'activité réseau, car il est lié à l'activité, mais pas au cycle de vie de l'activité. La rotation de l'appareil avec cette tâche est en cours d'exécution provoquera une exception et plantera votre application. Utilisez plutôt un IntentService qui supprime les données dans la base de données sqlite.
Brill Pappin
5
Attention, AsyncTask est souvent utilisé pour les opérations réseau par activité alors qu'il ne devrait pas l'être. son cycle de vie n'est pas en phase avec l'activité. Pour récupérer des données, vous devez utiliser un IntentService et la base de données derrière la vue.
Brill Pappin
1
@BrillPappin, FWIW, ce guide du développeur Android utilise AsyncTasket prend en charge un changement de configuration.
HeyJude
678

Vous devez presque toujours exécuter des opérations réseau sur un thread ou en tant que tâche asynchrone.

Mais il est possible de supprimer cette restriction et vous remplacez le comportement par défaut, si vous êtes prêt à accepter les conséquences.

Ajouter:

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();

StrictMode.setThreadPolicy(policy); 

Dans votre classe,

et

AJOUTER cette autorisation dans le fichier android manifest.xml:    

<uses-permission android:name="android.permission.INTERNET"/>

Conséquences:

Votre application (dans les zones de connexion Internet inégale) ne répond plus et se bloque, l'utilisateur perçoit la lenteur et doit effectuer une mise à mort forcée, et vous risquez que le gestionnaire d'activité tue votre application et informe l'utilisateur que l'application est arrêtée.

Android a quelques bons conseils sur les bonnes pratiques de programmation à concevoir pour la réactivité: http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html

user1169115
la source
456
C'est une très mauvaise idée. la solution est d'éviter les E / S réseau sur le thread principal (comme le montre la réponse acceptée).
MByD
74
Avec cela, vous ne cachez que votre vrai problème.
Alex
28
@TwistedUmbrella L'AsyncTask n'ajoute pas une page de code, il ajoute 6 lignes (déclaration de classe, annotation de substitution, doInBackgrounddéclaration, 2 crochets de fermeture et un appel à execute()). D'un autre côté, même une seule extraction depuis un site, comme vous le mentionnez, entraîne un retard important dans la réactivité de l'interface utilisateur. Ne sois pas paresseux.
Zoltán
19
Je pense que c'est la solution parfaite si vous cherchez à simplement exécuter un échantillon de code pour voir si quelque chose fonctionne avant de mettre en œuvre la bonne AsyncTask. C'est pourquoi j'ai voté en faveur de cette réponse, bien que, comme tous les autres l'ont dit, cela ne devrait pas être fait dans une application de production, seulement comme une solution rapide pour un test de développement.
hooked82
94
A voté. Cette réponse est correcte, et pour les nombreux programmeurs qui ne sont ni naïfs ni stupides, mais qui ont simplement besoin d'un appel SYNCHRONE (c'est-à-dire: cela doit bloquer l'application ), c'est exactement ce qui est nécessaire. Je suis plus qu'heureux qu'Android lève l'exception par défaut (à mon humble avis, c'est une chose très "utile" à faire!) - mais je suis tout aussi heureux de dire "merci, mais - c'est en fait ce que je voulais" et de passer outre il.
Adam
428

J'ai résolu ce problème en utilisant un nouveau Thread.

Thread thread = new Thread(new Runnable() {

    @Override
    public void run() {
        try  {
            //Your code goes here
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});

thread.start(); 
Dr.Luiji
la source
7
Au lieu de créer un nouveau thread à chaque fois que vous souhaitez effectuer une opération réseau, vous pouvez également utiliser un service d'exécution de thread unique.
Alex Lockwood
66
Simple mais dangereux. La Runnable anonyme a une référence implicite à la classe englobante (par exemple votre activité ou votre fragment), l'empêchant d'être récupérée jusqu'à la fin du thread. Vous devez au moins définir la priorité sur Process.BACKGROUND, sinon ce thread s'exécutera avec la même priorité que le thread principal / ui, en tenant compte des méthodes de cycle de vie et des fréquences d'images ui (attention aux avertissements dans le journal du chorégraphe).
Stevie
1
@Stevie comment définir la priorité? ni runnble ni executorService n'ont une telle méthode de définition
JK
1
@JK Fournissez à votre ExecutorService un ThreadFactory personnalisé et appelez Thread.setPriority sur le thread avant de le renvoyer.
Stevie
1
"L'utilisation de Threads directement dans Android est déconseillée. Elle cause plus de problèmes qu'elle n'en résout" Vous voulez développer cela? En fait, AsyncTask est obsolète exactement pour cela ... techyourchance.com/asynctask-deprecated
Fran Marzoa
170

La réponse acceptée présente des inconvénients importants. Il n'est pas conseillé d'utiliser AsyncTask pour la mise en réseau à moins que vous ne sachiez vraiment ce que vous faites. Certains des inconvénients comprennent:

  • Les AsyncTask créées en tant que classes internes non statiques ont une référence implicite à l'objet Activity englobant, à son contexte et à la hiérarchie View entière créée par cette activité. Cette référence empêche l'activité d'être récupérée de la mémoire jusqu'à ce que le travail en arrière-plan de AsyncTask se termine. Si la connexion de l'utilisateur est lente et / ou le téléchargement est important, ces fuites de mémoire à court terme peuvent devenir un problème - par exemple, si l'orientation change plusieurs fois (et que vous n'annulez pas les tâches en cours d'exécution), ou l'utilisateur s'éloigne de l'activité.
  • AsyncTask a des caractéristiques d'exécution différentes selon la plate-forme sur laquelle il s'exécute: avant le niveau 4 de l'API, les AsyncTasks s'exécutent en série sur un seul thread d'arrière-plan; du niveau API 4 au niveau API 10, les tâches AsyncTasks s'exécutent sur un pool de 128 threads au maximum; à partir du niveau 11 de l'API, AsyncTask s'exécute en série sur un seul thread d'arrière-plan (sauf si vous utilisez la executeOnExecutorméthode surchargée et fournissez un exécuteur alternatif). Le code qui fonctionne correctement lors de l'exécution en série sur ICS peut se casser lorsqu'il est exécuté simultanément sur Gingerbread, par exemple si vous avez des dépendances par défaut d'exécution.

Si vous souhaitez éviter les fuites de mémoire à court terme, avoir des caractéristiques d'exécution bien définies sur toutes les plates-formes et disposer d'une base pour créer une gestion de réseau vraiment robuste, vous pouvez envisager:

  1. Utiliser une bibliothèque qui fait un bon travail pour vous - il y a une bonne comparaison des bibliothèques de réseau dans cette question , ou
  2. Utiliser un Serviceou à la IntentServiceplace, peut-être avec un PendingIntentpour retourner le résultat via la onActivityResultméthode de l'activité .

Approche IntentService

Inconvénients:

  • Plus de code et de complexité que AsyncTaskvous ne le pensez, mais pas autant
  • Va mettre les demandes en file d'attente et les exécuter sur un seul thread d'arrière-plan. Vous pouvez facilement contrôler cela en remplaçant IntentServicepar une Serviceimplémentation équivalente , peut-être comme celle-ci .
  • Hum, je ne peux penser à aucun autre en ce moment

À l'envers:

  • Évite le problème de fuite de mémoire à court terme
  • Si votre activité redémarre alors que les opérations réseau sont en cours, elle peut toujours recevoir le résultat du téléchargement via sa onActivityResultméthode
  • Meilleure plate-forme que AsyncTask pour créer et réutiliser du code réseau robuste. Exemple: si vous devez effectuer un téléchargement important, vous pouvez le faire à partir AsyncTaskd'un Activity, mais si l'utilisateur change de contexte de l'application pour prendre un appel téléphonique, le système peut tuer l'application avant la fin du téléchargement. Il est moins probable de tuer une application avec un actif Service.
  • Si vous utilisez votre propre version simultanée de IntentService(comme celle que j'ai liée ci-dessus), vous pouvez contrôler le niveau de concurrence via le Executor.

Résumé de la mise en œuvre

Vous pouvez implémenter un IntentServicepour effectuer des téléchargements sur un seul thread d'arrière-plan assez facilement.

Étape 1: créez un IntentServicepour effectuer le téléchargement. Vous pouvez lui dire quoi télécharger via des Intentextra, et le passer PendingIntentà utiliser pour retourner le résultat à 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 re-use, 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);
        }
    }
}

Étape 2: inscrivez le service dans le manifeste:

<service
        android:name=".DownloadIntentService"
        android:exported="false"/>

Étape 3: appelez le service à partir de l'activité, en passant un objet PendingResult que le service utilisera pour renvoyer le résultat:

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);

Étape 4: gérer le résultat dans 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 projet Github contenant un projet Android-Studio / Gradle complet est disponible ici .

Stevie
la source
IntentService est la bonne façon de le faire, pas le déracinement car AsyncTask est exactement la façon de ne pas le faire.
Brill Pappin
3
@BrillPappin Je suis presque entièrement d'accord et j'ai reformulé pour souligner les inconvénients d'AsyncTask. (Je pense toujours qu'il existe un très petit nombre de cas où - si vous savez vraiment ce que vous faites - il pourrait être correct d'utiliser AsyncTask, mais la réponse acceptée ne souligne aucun inconvénient et est beaucoup trop populaire pour le bien d'Android).
Stevie
1
En avez-vous réellement besoin IllustrativeRSS? Et si vous ne travaillez pas avec des trucs RSS?
Cullub
À mon avis, Google devrait changer leur mauvaise implémentation de la collecte des ordures plutôt que de mettre le berdon du côté des programmeurs. C'est la responsabilité d'OS. Si un programmeur utilise IntentService ou Service pour faire le travail en raison du problème fondamental de la mise en œuvre d'Android, après quelques années, Google dira que IntentService est également une mauvaise pratique et suggère autre chose. Et cette histoire continue ... Les développeurs de Google devraient donc résoudre la mauvaise gestion de la mémoire Android et non les programmeurs.
saeed khalafinejad
Je vais juste le dire: cette réponse semble être plus un moyen de partager le projet qu'une meilleure alternative à AsyncTask. L'erreur était destinée à empêcher les développeurs de ralentir l'interface utilisateur, pas nécessairement les inciter à risquer la sécurité avec un tas d'intentions / services.
Panier abandonné le
144

Vous ne pouvez pas effectuer d' E / S réseau sur le thread d'interface utilisateur sur Honeycomb . Techniquement, cela est possible sur les versions antérieures d'Android, mais c'est une très mauvaise idée car cela entraînera la cessation de votre application et le système d'exploitation peut tuer votre application pour son mauvais comportement. Vous devrez exécuter un processus d'arrière-plan ou utiliser AsyncTask pour effectuer votre transaction réseau sur un thread d'arrière-plan.

Il y a un article sur le filetage indolore sur le site des développeurs Android qui est une bonne introduction à cela, et il vous fournira une bien meilleure profondeur de réponse que ce qui peut être réaliste ici.

Mark Allison
la source
76
  1. N'utilisez pas strictMode (uniquement en mode débogage)
  2. Ne changez pas la version du SDK
  3. N'utilisez pas de fil séparé

Utiliser Service ou AsyncTask

Voir aussi la question Stack Overflow:

android.os.NetworkOnMainThreadException envoi d'un e-mail à partir d'Android

venergiac
la source
8
Il convient peut-être de souligner que si vous utilisez un service, vous devrez toujours créer un thread distinct - les rappels de service s'exécutent sur le thread principal. Un IntentService, d'autre part, exécute sa méthode onHandleIntent sur un thread d'arrière-plan.
Stevie
vous ne devez pas utiliser une AsyncTask pour les opérations de longue durée! Les directives spécifient 2 à 3 secondes max.
Dage
76

Faites les actions du réseau sur un autre thread

Par exemple:

new Thread(new Runnable(){
    @Override
    public void run() {
        // Do network action in this function
    }
}).start();

Et ajoutez ceci à AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>
henry4343
la source
5
Mais comment pouvons-nous savoir quand le thread se termine pour que nous puissions effectuer le prochain ensemble de tâches dans le thread d'interface utilisateur? L'AsyncTask offre la possibilité de le faire. Existe-t-il un moyen de faire de même en utilisant des threads exécutables?
Piyush Soni
3
Il traitera votre code étape par étape, donc à la fin du code, vous devez utiliser le gestionnaire de retour au thread d'interface utilisateur
henry4343
1
Vous pouvez utiliser une tâche asynchrone ou un service d'intention, car il s'exécute sur le thread de travail.
Chetan Chaudhari
63

Vous désactivez le mode strict à l'aide du code suivant:

if (android.os.Build.VERSION.SDK_INT > 9) {
    StrictMode.ThreadPolicy policy = 
        new StrictMode.ThreadPolicy.Builder().permitAll().build();
    StrictMode.setThreadPolicy(policy);
}

Ce n'est pas recommandé : utilisez l' AsyncTaskinterface.

Code complet pour les deux méthodes

Sandeep
la source
2
Oui, une erreur ANR serait survenue. signifie que l'application ne répond pas dans 5 secondes.
Muhammad Mubashir
12
C'est une très mauvaise réponse. Vous ne devez pas changer la politique du thread mais pour écrire un meilleur code: ne faites pas d'opérations réseau sur le thread principal!
shkschneider
@Sandeep Vous et les autres téléspectateurs devriez également lire ceci. stackoverflow.com/a/18335031/3470479
Prakhar1001
53

Les opérations réseau ne peuvent pas être exécutées sur le thread principal. Vous devez exécuter toutes les tâches réseau sur un thread enfant ou implémenter AsyncTask.

Voici comment exécuter une tâche dans un thread enfant:

new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            // Your implementation goes here
        } 
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}).start();
Dhruv Jindal
la source
1
Runnable anonyme n'est PAS le meilleur moyen, car il a une référence implicite à la classe englobante et l'empêche d'être GC ed jusqu'à ce que le thread se termine! De plus, ce thread s'exécutera à la même priorité que le thread principal / américain, en concurrence avec les méthodes de cycle de vie et les fréquences d'images de l'interface utilisateur!
Yousha Aleayoub
49

Mettez votre code à l'intérieur:

new Thread(new Runnable(){
    @Override
    public void run() {
        try {
            // Your implementation
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}).start();

Ou:

class DemoTask extends AsyncTask<Void, Void, Void> {

    protected Void doInBackground(Void... arg0) {
        //Your implementation
    }

    protected void onPostExecute(Void result) {
        // TODO: do something with the feed
    }
}
Vaishali Sutariya
la source
Le second sera meilleur que le premier pour une API supérieure à 11
Rohit Goswami
46

Cela se produit dans Android 3.0 et supérieur. À partir d'Android 3.0 et versions ultérieures, ils ont restreint l'utilisation des opérations réseau (fonctions qui accèdent à Internet) pour s'exécuter dans le thread principal / thread d'interface utilisateur (ce qui apparaît à partir des méthodes de création et de reprise de l'activité).

Il s'agit d'encourager l'utilisation de threads séparés pour les opérations réseau. Voir AsyncTask pour plus de détails sur la façon d'effectuer correctement les activités réseau.

raihan ahmed
la source
46

L'utilisation d' annotations Android est une option. Il vous permettra d'exécuter simplement n'importe quelle méthode dans un thread d'arrière-plan:

// normal method
private void normal() {
    doSomething(); // do something in background
}

@Background
protected void doSomething() 
    // run your networking code here
}

Notez que, bien qu'il offre des avantages de simplicité et de lisibilité, il présente ses inconvénients.

Oleksiy
la source
6
@Gavriel, il crée des doublons de tout ce que vous annotez, que ce soit une méthode, une activité, un fragment, un singleton, etc., il y a donc deux fois plus de code et il faut plus de temps pour le compiler. Il peut également y avoir des problèmes en raison de bogues dans la bibliothèque. Le débogage et la recherche d'erreurs deviendraient plus difficiles.
Oleksiy
43

L'erreur est due à l'exécution de longues opérations dans le thread principal. Vous pouvez facilement corriger le problème en utilisant AsynTask ou Thread . Vous pouvez extraire cette bibliothèque AsyncHTTPClient pour une meilleure gestion.

AsyncHttpClient client = new AsyncHttpClient();
client.get("http://www.google.com", new AsyncHttpResponseHandler() {

    @Override
    public void onStart() {
        // Called before a request is started
    }

    @Override
    public void onSuccess(int statusCode, Header[] headers, byte[] response) {
        // Called when response HTTP status is "200 OK"
    }

    @Override
    public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
        // Called when response HTTP status is "4XX" (for example, 401, 403, 404)
    }

    @Override
    public void onRetry(int retryNo) {
        // Called when request is retried
    }
});
Ashwin S Ashok
la source
42

Vous ne devez pas effectuer de tâche longue sur le thread principal (thread d'interface utilisateur), comme toute opération réseau, les E / S de fichiers ou les opérations de base de données SQLite. Donc, pour ce type d'opération, vous devez créer un thread de travail, mais le problème est que vous ne pouvez pas effectuer directement d'opération liée à l'interface utilisateur à partir de votre thread de travail. Pour cela, vous devez utiliser Handleret passer leMessage .

Pour simplifier toutes ces choses, Android fournit diverses manières, comme AsyncTask, AsyncTaskLoader, CursorLoaderou IntentService. Vous pouvez donc utiliser n'importe lequel de ces éléments selon vos besoins.

Cuves Kapil
la source
40

La meilleure réponse de spektom fonctionne parfaitement.

Si vous écrivez l' AsyncTaskinline et ne l'étendez pas en tant que classe, et en plus de cela, s'il est nécessaire d'obtenir une réponse AsyncTask, vous pouvez utiliser la get()méthode ci-dessous.

RSSFeed feed = new RetreiveFeedTask().execute(urlToRssFeed).get();

(D'après son exemple.)

sivag1
la source
5
utiliser get()est une mauvaise idée ... cela permet à AsyncTask de se "synchroniser" à nouveau
Selvin
Existe-t-il une meilleure solution différente? @Selvin
sivag1
2
Je pense que vous pouvez informer le thread principal du résultat. Par exemple, envoyez une diffusion au thread principal, y compris le résultat.
Chine Gary
32

Ceci est uniquement lancé pour les applications ciblant le nid d'abeille SDK ou supérieur. Les applications ciblant les versions antérieures du SDK sont autorisées à faire du réseautage sur leurs principaux threads de boucle d'événements.

L'erreur est l'avertissement du SDK!

poiré
la source
28

Pour moi, c'était ça:

<uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="10" />

L'appareil sur lequel je testais mon application était 4.1.2, qui est la version 16 du SDK!

Assurez-vous que la version cible est la même que votre bibliothèque cible Android. Si vous ne savez pas quelle est votre bibliothèque cible, cliquez avec le bouton droit sur votre projet -> Chemin de génération -> Android , et ce devrait être celui qui est coché.

En outre, comme d'autres l'ont mentionné, incluez les autorisations appropriées pour accéder à Internet:

<uses-permission android:name="android.permission.INTERNET"/>
rharvey
la source
11
Permettez-moi de vous expliquer ce que vous faites ici: NetworkOnMainThreadExceptionest le Guardian qui vous dit: ne tirez pas à votre propre pied ... votre solution est: revenons au passé quand il n'y avait pas de Guardian - maintenant je peux tirer sur mon pied librement
Selvin
1
J'ai également adopté cette approche et je n'ai eu aucun problème. Le gardien est parfois trop difficile.
FractalBob
25

Utilisez ceci dans votre activité

    btnsub.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub

                    //Initialize soap request + add parameters
                    SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME1);

                    //Use this to add parameters
                    request.addProperty("pincode", txtpincode.getText().toString());
                    request.addProperty("bg", bloodgroup.getSelectedItem().toString());

                    //Declare the version of the SOAP request
                    SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);

                    envelope.setOutputSoapObject(request);
                    envelope.dotNet = true;

                    try {
                        HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);

                        //this is the actual part that will call the webservice
                        androidHttpTransport.call(SOAP_ACTION1, envelope);

                        // Get the SoapResult from the envelope body.
                        SoapObject result = (SoapObject) envelope.getResponse();
                        Log.e("result data", "data" + result);
                        SoapObject root = (SoapObject) result.getProperty(0);
                        // SoapObject s_deals = (SoapObject) root.getProperty(0);
                        // SoapObject s_deals_1 = (SoapObject) s_deals.getProperty(0);
                        //

                        System.out.println("********Count : " + root.getPropertyCount());

                        value = new ArrayList<Detailinfo>();

                        for (int i = 0; i < root.getPropertyCount(); i++) {
                            SoapObject s_deals = (SoapObject) root.getProperty(i);
                            Detailinfo info = new Detailinfo();

                            info.setFirstName(s_deals.getProperty("Firstname").toString());
                            info.setLastName(s_deals.getProperty("Lastname").toString());
                            info.setDOB(s_deals.getProperty("DOB").toString());
                            info.setGender(s_deals.getProperty("Gender").toString());
                            info.setAddress(s_deals.getProperty("Address").toString());
                            info.setCity(s_deals.getProperty("City").toString());
                            info.setState(s_deals.getProperty("State").toString());
                            info.setPinecode(s_deals.getProperty("Pinecode").toString());
                            info.setMobile(s_deals.getProperty("Mobile").toString());
                            info.setEmail(s_deals.getProperty("Email").toString());
                            info.setBloodgroup(s_deals.getProperty("Bloodgroup").toString());
                            info.setAdddate(s_deals.getProperty("Adddate").toString());
                            info.setWaight(s_deals.getProperty("waight").toString());
                            value.add(info);
                        }

                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    Intent intent = new Intent(getApplicationContext(), ComposeMail.class);
                    //intent.putParcelableArrayListExtra("valuesList", value);

                    startActivity(intent);
                }
            }).start();
        }
    });
dhiraj kakran
la source
23

Juste pour énoncer explicitement quelque chose:

Le thread principal est essentiellement le thread d'interface utilisateur.

Donc, dire que vous ne pouvez pas faire d'opérations de mise en réseau dans le thread principal signifie que vous ne pouvez pas faire d'opérations de mise en réseau dans le thread d'interface utilisateur, ce qui signifie que vous ne pouvez pas faire d'opérations de mise en réseau dans un *runOnUiThread(new Runnable() { ... }*bloc intérieur d'un autre thread.

(J'ai juste passé un long moment à me gratter la tête en essayant de comprendre pourquoi j'obtenais cette erreur ailleurs que dans mon fil principal. C'est pourquoi; ce fil a aidé; et j'espère que ce commentaire aidera quelqu'un d'autre.)

Novak
la source
22

Cette exception se produit en raison de toute tâche lourde effectuée sur le thread principal si cette tâche d'exécution prend trop de temps .

Pour éviter cela, nous pouvons le gérer en utilisant des threads ou des exécuteurs

Executors.newSingleThreadExecutor().submit(new Runnable() {
    @Override
    public void run() {
        // You can perform your task here.
    }
});
amardeep
la source
18

Il y a déjà beaucoup de bonnes réponses à cette question, mais beaucoup de bonnes bibliothèques sont sorties depuis que ces réponses ont été publiées. Il s'agit d'une sorte de guide pour les débutants.

Je couvrirai plusieurs cas d'utilisation pour effectuer des opérations réseau et une solution ou deux pour chacun.

ReST sur HTTP

Typiquement Json, peut être XML ou autre

Accès complet à l'API

Disons que vous écrivez une application qui permet aux utilisateurs de suivre les cours des actions, les taux d'intérêt et les taux de change en cours. Vous trouvez une API Json qui ressemble à ceci:

http://api.example.com/stocks                       //ResponseWrapper<String> object containing a list of Srings with ticker symbols
http://api.example.com/stocks/$symbol               //Stock object
http://api.example.com/stocks/$symbol/prices        //PriceHistory<Stock> object
http://api.example.com/currencies                   //ResponseWrapper<String> object containing a list of currency abbreviation
http://api.example.com/currencies/$currency         //Currency object
http://api.example.com/currencies/$id1/values/$id2  //PriceHistory<Currency> object comparing the prices of the first currency (id1) to the second (id2)

Rénovation de Square

C'est un excellent choix pour une API avec plusieurs points de terminaison et vous permet de déclarer les points de terminaison ReST au lieu d'avoir à les coder individuellement comme avec d'autres bibliothèques comme ion ou Volley. (site Web: http://square.github.io/retrofit/ )

Comment l'utilisez-vous avec l'API finances?

build.gradle

Ajoutez ces lignes à votre niveau de module buid.gradle:

implementation 'com.squareup.retrofit2:retrofit:2.3.0' //retrofit library, current as of September 21, 2017
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' //gson serialization and deserialization support for retrofit, version must match retrofit version

FinancesApi.java

public interface FinancesApi {
    @GET("stocks")
    Call<ResponseWrapper<String>> listStocks();
    @GET("stocks/{symbol}")
    Call<Stock> getStock(@Path("symbol")String tickerSymbol);
    @GET("stocks/{symbol}/prices")
    Call<PriceHistory<Stock>> getPriceHistory(@Path("symbol")String tickerSymbol);

    @GET("currencies")
    Call<ResponseWrapper<String>> listCurrencies();
    @GET("currencies/{symbol}")
    Call<Currency> getCurrency(@Path("symbol")String currencySymbol);
    @GET("currencies/{symbol}/values/{compare_symbol}")
    Call<PriceHistory<Currency>> getComparativeHistory(@Path("symbol")String currency, @Path("compare_symbol")String currencyToPriceAgainst);
}

FinancesApiBuilder

public class FinancesApiBuilder {
    public static FinancesApi build(String baseUrl){
        return new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                    .create(FinancesApi.class);
    }
}

Extrait de fragment

FinancesApi api = FinancesApiBuilder.build("http://api.example.com/"); //trailing '/' required for predictable behavior
api.getStock("INTC").enqueue(new Callback<Stock>(){
    @Override
    public void onResponse(Call<Stock> stockCall, Response<Stock> stockResponse){
        Stock stock = stockCall.body();
        //do something with the stock
    }
    @Override
    public void onResponse(Call<Stock> stockCall, Throwable t){
        //something bad happened
    }
}

Si votre API nécessite l'envoi d'une clé API ou d'un autre en-tête comme un jeton d'utilisateur, etc., Retrofit facilite cela (voir cette réponse géniale pour plus de détails: https://stackoverflow.com/a/42899766/1024412 ).

Accès unique à l'API ReST

Imaginons que vous construisiez une application "météo d'humeur" qui recherche la position GPS des utilisateurs et vérifie la température actuelle dans cette zone et leur indique l'ambiance. Ce type d'application n'a pas besoin de déclarer des points de terminaison API; il doit juste être en mesure d'accéder à un point de terminaison API.

Ion

Il s'agit d'une excellente bibliothèque pour ce type d'accès.

Veuillez lire l'excellente réponse de msysmilu ( https://stackoverflow.com/a/28559884/1024412 )

Charger des images via HTTP

Volée

Volley peut également être utilisé pour les API ReST, mais en raison de la configuration plus complexe requise, je préfère utiliser Retrofit from Square comme ci-dessus ( http://square.github.io/retrofit/ )

Supposons que vous créez une application de réseautage social et que vous souhaitez charger des photos de profil d'amis.

build.gradle

Ajoutez cette ligne à votre niveau de module buid.gradle:

implementation 'com.android.volley:volley:1.0.0'

ImageFetch.java

Volley nécessite plus de configuration que Retrofit. Vous devrez créer une classe comme celle-ci pour configurer un RequestQueue, un ImageLoader et un ImageCache, mais ce n'est pas trop mal:

public class ImageFetch {
    private static ImageLoader imageLoader = null;
    private static RequestQueue imageQueue = null;

    public static ImageLoader getImageLoader(Context ctx){
        if(imageLoader == null){
            if(imageQueue == null){
                imageQueue = Volley.newRequestQueue(ctx.getApplicationContext());
            }
            imageLoader = new ImageLoader(imageQueue, new ImageLoader.ImageCache() {
                Map<String, Bitmap> cache = new HashMap<String, Bitmap>();
                @Override
                public Bitmap getBitmap(String url) {
                    return cache.get(url);
                }
                @Override
                public void putBitmap(String url, Bitmap bitmap) {
                    cache.put(url, bitmap);
                }
            });
        }
        return imageLoader;
    }
}

user_view_dialog.xml

Ajoutez ce qui suit à votre fichier xml de mise en page pour ajouter une image:

<com.android.volley.toolbox.NetworkImageView
    android:id="@+id/profile_picture"
    android:layout_width="32dp"
    android:layout_height="32dp"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    app:srcCompat="@android:drawable/spinner_background"/>

UserViewDialog.java

Ajoutez le code suivant à la méthode onCreate (Fragment, Activity) ou au constructeur (Dialog):

NetworkImageView profilePicture = view.findViewById(R.id.profile_picture);
profilePicture.setImageUrl("http://example.com/users/images/profile.jpg", ImageFetch.getImageLoader(getContext());

Picasso

Une autre excellente bibliothèque de Square. Veuillez consulter le site pour de bons exemples: http://square.github.io/picasso/

KG6ZVP
la source
16

En termes simples,

NE PAS FAIRE DE TRAVAIL EN RÉSEAU DANS LE FIL D'UI

Par exemple, si vous effectuez une requête HTTP, il s'agit d'une action réseau.

Solution:

  1. Vous devez créer un nouveau fil
  2. Ou utilisez la classe AsyncTask

Façon:

Mettez toutes vos œuvres à l'intérieur

  1. run() méthode du nouveau thread
  2. Ou doInBackground() méthode de la classe AsyncTask.

Mais:

Lorsque vous obtenez quelque chose de la réponse du réseau et que vous souhaitez l'afficher sur votre vue (comme afficher le message de réponse dans TextView), vous devez revenir au thread de l'interface utilisateur .

Si vous ne le faites pas, vous obtiendrez ViewRootImpl$CalledFromWrongThreadException.

Comment?

  1. Lors de l'utilisation d'AsyncTask, mettez à jour la vue à partir de la onPostExecute()méthode
  2. Ou appelez la runOnUiThread()méthode et mettez à jour la vue à l'intérieur de la run()méthode.
Nabin
la source
12

Vous êtes en mesure de déplacer une partie de votre code dans un autre thread pour décharger le main threadet éviter d'obtenir ANR , NetworkOnMainThreadException , IllegalStateException (par exemple, impossible d'accéder à la base de données sur le thread principal car il peut potentiellement verrouiller l'interface utilisateur pendant une longue période de temps).

Il existe certaines approches que vous devez choisir en fonction de la situation

Thread Java ou Android HandlerThread

Les threads Java sont à usage unique et meurent après l'exécution de sa méthode d'exécution.

HandlerThread est une classe pratique pour démarrer un nouveau thread qui a un looper.

AsyncTask

AsyncTask est conçu pour être une classe d'assistance autour de Thread et Handler et ne constitue pas un cadre de thread générique. AsyncTasks devrait idéalement être utilisé pour des opérations courtes (quelques secondes tout au plus.) Si vous devez garder les threads en cours d'exécution pendant de longues périodes, il est fortement recommandé d'utiliser les différentes API fournies par le package java.util.concurrent tel que Executor , ThreadPoolExecutor et FutureTask .

Implémentation du pool de threads ThreadPoolExecutor , ScheduledThreadPoolExecutor ...

Classe ThreadPoolExecutor qui implémente ExecutorService qui donne un contrôle précis sur le pool de threads (par exemple, la taille du pool principal, la taille maximale du pool, le temps de maintien en vie, etc.)

ScheduledThreadPoolExecutor - une classe qui étend ThreadPoolExecutor. Il peut planifier des tâches après un certain délai ou périodiquement.

FutureTask

FutureTask effectue un traitement asynchrone, cependant, si le résultat n'est pas encore prêt ou le traitement n'est pas terminé, l'appel à get () bloquera le thread

AsyncTaskLoaders

AsyncTaskLoaders car ils résolvent de nombreux problèmes inhérents à AsyncTask

IntentService

C'est le choix de facto pour le traitement de longue durée sur Android, un bon exemple serait de télécharger ou de télécharger des fichiers volumineux. Le téléchargement et le téléchargement peuvent se poursuivre même si l'utilisateur quitte l'application et que vous ne voulez certainement pas empêcher l'utilisateur de pouvoir utiliser l'application pendant que ces tâches sont en cours.

JobScheduler

En effet, vous devez créer un service et créer un travail à l'aide de JobInfo.Builder qui spécifie vos critères pour quand exécuter le service.

RxJava

Bibliothèque pour composer des programmes asynchrones et basés sur des événements en utilisant des séquences observables.

Coroutines (Kotlin)

L'essentiel est que le code asynchrone ressemble beaucoup à synchrone

En savoir plus ici , ici , ici , ici

yoAlex5
la source
A fonctionné pour moi ... J'ai utilisé AsyncTask largement, mais quand une tâche est en cours d'exécution, une autre attendra. Je veux résoudre ça. Travaille maintenant avec executeonexecutor. Voyons comment il se comportera sur les périphériques à faible mémoire.
Suraj Shingade
Veuillez consulter la méthode: asyncTask.executeOnExecutor (AsyncTask.THREAD_POOL_EXECUTOR, params); pour exécuter votre tâche simultanément
yoAlex5
10

Bien qu'au-dessus il y ait un énorme pool de solutions, personne n'a mentionné com.koushikdutta.ion: https://github.com/koush/ion

Il est également asynchrone et très simple à utiliser:

Ion.with(context)
.load("http://example.com/thing.json")
.asJsonObject()
.setCallback(new FutureCallback<JsonObject>() {
   @Override
    public void onCompleted(Exception e, JsonObject result) {
        // do stuff with the result or error
    }
});
msysmilu
la source
10

Nouveau Threadet AsyncTask solutions ont déjà été expliquées.

AsyncTaskdevrait idéalement être utilisé pour de courtes opérations. OrdinaireThread n'est pas préférable pour Android.

Découvrez une solution alternative à l'aide de HandlerThread et Handler

HandlerThread

Classe pratique pour démarrer un nouveau thread qui a un looper. Le looper peut ensuite être utilisé pour créer des classes de gestionnaires. Notez que start()doit encore être appelé.

Gestionnaire:

Un gestionnaire vous permet d'envoyer et de traiter des objets Message et Runnable associés à MessageQueue d'un thread. Chaque instance de gestionnaire est associée à un seul thread et à la file d'attente de messages de ce thread. Lorsque vous créez un nouveau gestionnaire, il est lié à la file d'attente des threads / messages du thread qui le crée - à partir de ce moment, il fournira des messages et des exécutables à cette file d'attente de messages et les exécutera à la sortie du message. queue.

Solution:

  1. Créer HandlerThread

  2. appelez start()leHandlerThread

  3. Créez Handleren obtenant LooperdeHanlerThread

  4. Incorporez votre code lié aux opérations réseau dans l' Runnableobjet

  5. Soumettre la Runnabletâche àHandler

Exemple d'extrait de code, qui adresse NetworkOnMainThreadException

HandlerThread handlerThread = new HandlerThread("URLConnection");
handlerThread.start();
handler mainHandler = new Handler(handlerThread.getLooper());

Runnable myRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            Log.d("Ravi", "Before IO call");
            URL page = new URL("http://www.google.com");
            StringBuffer text = new StringBuffer();
            HttpURLConnection conn = (HttpURLConnection) page.openConnection();
            conn.connect();
            InputStreamReader in = new InputStreamReader((InputStream) conn.getContent());
            BufferedReader buff = new BufferedReader(in);
            String line;
            while ( (line =  buff.readLine()) != null) {
                text.append(line + "\n");
            }
            Log.d("Ravi", "After IO call");
            Log.d("Ravi",text.toString());

        }catch( Exception err){
            err.printStackTrace();
        }
    }
};
mainHandler.post(myRunnable);

Avantages d'utiliser cette approche:

  1. Créer de nouvelles Thread/AsyncTaskpour chaque opération de réseau coûte cher. Le Thread/AsyncTasksera détruit et recréé pour les prochaines opérations du réseau. Mais avec Handleret HandlerThreadapproche, vous pouvez soumettre de nombreuses opérations réseau (en tant que tâches exécutables) à une seule HandlerThreadà l'aide de Handler.
Ravindra babu
la source
8

Il existe un autre moyen très pratique de résoudre ce problème: utilisez les capacités de concurrence de rxJava. Vous pouvez exécuter n'importe quelle tâche en arrière-plan et publier les résultats sur le thread principal de manière très pratique, de sorte que ces résultats seront transmis à la chaîne de traitement.

Le premier conseil de réponse vérifié est d'utiliser AsynTask. Oui, c'est une solution, mais elle est obsolète de nos jours, car il y a de nouveaux outils autour.

String getUrl() {
    return "SomeUrl";
}

private Object makeCallParseResponse(String url) {
    return null;
    //
}

private void processResponse(Object o) {

}

La méthode getUrl fournit l'adresse URL et elle sera exécutée sur le thread principal.

makeCallParseResponse (..) - fait un travail réel

processResponse (..) - gérera le résultat sur le thread principal.

Le code pour l'exécution asynchrone ressemblera à:

rx.Observable.defer(new Func0<rx.Observable<String>>() {
    @Override
    public rx.Observable<String> call() {
        return rx.Observable.just(getUrl());
    }
})
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.io())
    .map(new Func1<String, Object>() {
        @Override
        public Object call(final String s) {
            return makeCallParseResponse(s);
        }
    })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Action1<Object>() {
        @Override
        public void call(Object o) {
             processResponse(o);
        }
    },
    new Action1<Throwable>() {
        @Override
        public void call(Throwable throwable) {
            // Process error here, it will be posted on
            // the main thread
        }
    });

Par rapport à AsyncTask, cette méthode permet de changer de planificateur un nombre arbitraire de fois (par exemple, récupérer des données sur un planificateur et traiter ces données sur un autre (par exemple, Scheduler.computation ()). Vous pouvez également définir vos propres planificateurs.

Afin d'utiliser cette bibliothèque, incluez les lignes suivantes dans votre fichier build.gradle:

   compile 'io.reactivex:rxjava:1.1.5'
   compile 'io.reactivex:rxandroid:1.2.0'

La dernière dépendance inclut la prise en charge du planificateur .mainThread ().

Il existe un excellent ebook pour rx-java .

Alex Shutov
la source
une approche assez vaste que ce serait planifié par nous-mêmes et je ne sais pas pourquoi nous devrions le faire si c'est déjà une option du côté Android?
Prakhar1001
8

RxAndroidest une autre meilleure alternative à ce problème et cela nous évite d'avoir à créer des threads et à publier des résultats sur le thread de l'interface utilisateur Android. Nous avons juste besoin de spécifier les threads sur lesquels les tâches doivent être exécutées et tout est géré en interne.

Observable<List<String>> musicShowsObservable = Observable.fromCallable(new Callable<List<String>>() { 

  @Override 
  public List<String> call() { 
    return mRestClient.getFavoriteMusicShows(); 
  }
});

mMusicShowSubscription = musicShowsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<String>>() {

    @Override 
    public void onCompleted() { }

    @Override 
    public void onError(Throwable e) { }

    @Override 
    public void onNext(List<String> musicShows){
        listMusicShows(musicShows);
    }
});
  1. En spécifiant (Schedulers.io()), RxAndroid s'exécutera getFavoriteMusicShows() sur un thread différent.

  2. En utilisant, AndroidSchedulers.mainThread()nous voulons observer cet observable sur le thread d'interface utilisateur, c'est-à-dire que nous voulons que notre onNext()rappel soit appelé sur le thread d'interface utilisateur

Shinoo Goyal
la source
8

Le thread principal est le thread d'interface utilisateur, et vous ne pouvez pas effectuer une opération dans le thread principal qui peut bloquer l'interaction utilisateur. Vous pouvez résoudre ce problème de deux manières:

Forcer à faire la tâche dans le thread principal comme ceci

StrictMode.ThreadPolicy threadPolicy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(threadPolicy);

Ou créez un gestionnaire simple et mettez à jour le thread principal si vous le souhaitez.

Runnable runnable;
Handler newHandler;

newHandler = new Handler();
runnable = new Runnable() {
    @Override
    public void run() {
         try {
            //update UI
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
};
newHandler.post(runnable);

Et pour arrêter l'utilisation du fil:

newHandler.removeCallbacks(runnable);

Pour plus d'informations, vérifiez ceci: Filetage indolore

Sharath kumar
la source
THX. La version 1 aide lors de l'ajout en tant que première action dans onCreate.
Ingo
7

Cela marche. Je viens de rendre la réponse du Dr Luiji un peu plus simple.

new Thread() {
    @Override
    public void run() {
        try {
            //Your code goes here
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}.start();
Kacy
la source
7

Sur Android, les opérations réseau ne peuvent pas être exécutées sur le thread principal. Vous pouvez utiliser Thread, AsyncTask (tâches à exécution courte), Service (tâches à exécution longue) pour effectuer des opérations réseau.

Ponsuyambu Velladurai
la source