Pytanie Jak naprawić android.os.NetworkOnMainThreadException?


Wystąpił błąd podczas uruchamiania mojego projektu Android dla RssReader.

Kod:

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

I pokazuje poniższy błąd:

android.os.NetworkOnMainThreadException

Jak mogę rozwiązać ten problem?


1981
2018-06-14 12:02


pochodzenie


Przeczytaj ten post na blogu w obiekcie NetworkOnMainThreadException, aby uzyskać więcej informacji. Wyjaśnia, dlaczego tak się dzieje w systemie Android 3.0 lub nowszym. - Adrian Monk
Aby być na rytualnym utworze najpierw przeczytaj o Żądaniach Sieciowych w Androidzie, wtedy poleciłbym studiować "Volley". - Anuj Sharma
Istnieje wiele alternatywnych bibliotek, które rozwiązują ten problem. Wiele jest wymienionych na dole tej strony. Jeśli masz więcej, bierzemy je :) - Snicolas
Musisz uruchomić działania internetowe w wątku innym niż wątek główny (UI) - Naveed Ahmad
stackoverflow.com/questions/16439587/...    powyższy link jest dobry - nguyenvangiangbn


Odpowiedzi:


Ten wyjątek jest generowany, gdy aplikacja próbuje wykonać operację sieciową na głównym wątku. Uruchom swój kod 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
    }
}

Jak wykonać zadanie:

W MainActivity.java plik możesz dodać ten wiersz w swoim oncreate() metoda

new RetrieveFeedTask().execute(urlToRssFeed);

Nie zapomnij dodać tego do AndroidManifest.xml plik:

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

2240
2018-06-14 12:15



Myślę, że warto tutaj zauważyć, że powyższy fragment kodu ma być podklasą (klasa wewnętrzna), najlepiej prywatną. W ten sposób, gdy kończy się AsyncTask, nadal możesz manipulować wnętrznościami swojej klasy. - dyslexicanaboko
Właściwie zrobiłem to samo, co wspomniałeś powyżej, ale w obliczu tego błędu java.lang.RuntimeException: Nie można utworzyć programu obsługi wewnątrz wątku, który nie nazwał Looper.prepare () - Dhruv Tyagi
To jest zła odpowiedź. Natrafiam na to cały czas w kodzie ludzi i denerwuję się, że muszę to naprawić cały czas. AsyncTask nie powinien być używany do aktywności sieciowej, ponieważ jest powiązany z działaniem, ale nie z cyklem życia aktywności. Obrócenie urządzenia z uruchomionym zadaniem spowoduje wyjątek i awarię aplikacji. Zamiast tego użyj usługi IntentService, która powoduje utratę danych w bazie danych SQLite. - Brill Pappin
Uwaga: AsyncTask jest często używany w operacjach sieciowych, gdy nie powinien. jego cykl życia nie jest zsynchronizowany z działaniem. Do pobierania danych należy użyć IntentService i bazy danych za widokiem. - Brill Pappin
W takich przypadkach gorąco polecam AsyncTask Loader. - Roman Gherta


Powinieneś prawie zawsze uruchamiać operacje sieciowe w wątku lub jako zadanie asynchroniczne.

Ale to jest można usunąć to ograniczenie i zastąpić domyślne zachowanie, jeśli chcesz zaakceptować konsekwencje.

Dodaj:

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

StrictMode.setThreadPolicy(policy); 

W Twojej klasie,

i

DODAJ to uprawnienie w pliku manifest.xml na Androida:

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

Konsekwencje:

Twoja aplikacja (w obszarach o nierównym połączeniu internetowym) przestanie reagować i się zablokuje, użytkownik odczuwa powolność i musi zabić siłę, ryzykując, że menedżer aktywności zabije twoją aplikację i poinformuje użytkownika, że ​​aplikacja się zatrzymała.

Android ma kilka dobrych wskazówek dotyczących dobrych praktyk programistycznych do projektowania pod kątem reagowania: http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html


557
2018-02-15 06:59



To bardzo zły pomysł. rozwiązaniem jest uniknięcie sieciowego IO w głównym wątku (jak pokazuje zaakceptowana odpowiedź). - MByD
Dzięki temu ukrywasz swój prawdziwy problem. - Alex
@TwistedUmbrella AsyncTask nie dodaje strony kodu, dodaje 6 wierszy (deklaracja klasy, nadpisanie adnotacji, doInBackground deklaracja, 2 nawiasy zamykające i wezwanie do execute()). Z drugiej strony nawet pojedyncze pobranie ze strony, o której wspominasz, znacznie opóźnia reakcję interfejsu. Nie bądź leniwy. - Zoltán
@TwistedUmbrella Nie bądź głupi. Tak powstają złe aplikacje. Niezależnie od tego, czy pobierasz plik tekstowy, czy galerię obrazów, postępuj zgodnie ze sprawdzonymi metodami. Uruchomienie sieciowego IO na głównym wątku nie jest najlepszą praktyką. - States
Rewizja. Ta odpowiedź jest poprawna i dla wielu programistów, którzy nie są ani naiwni, ani głupi, ale po prostu wymagają SYNCHRONICZNEGO (tj .: musi zablokować aplikację) zadzwoń, to jest dokładnie to, czego potrzebujesz. Jestem bardziej niż szczęśliwy, że Android domyślnie zgłasza wyjątek (IMHO to bardzo "pomocna rzecz"!) - ale jestem równie szczęśliwy, gdy mówię "dziękuję, ale - to jest dokładnie to, co zamierzałem" i zastąpić to. - Adam


Rozwiązałem ten problem, używając nowego Thread.

Thread thread = new Thread(new Runnable() {

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

thread.start(); 

346
2018-01-21 16:31



Zamiast tworzenia nowego wątku za każdym razem, gdy chcesz wykonać operację sieciową, możesz również użyć usługi jednego executora wątków. - Alex Lockwood
Prosty, ale niebezpieczny. Anonimowy Runnable ma niejawne odniesienie do klasy otaczającej (np. Twojej aktywności lub fragmentu), uniemożliwiając jej zbieranie ze śmieci do momentu zakończenia wątku. Powinieneś przynajmniej ustawić priorytet Process.BACKGROUND, w przeciwnym razie wątek ten będzie działał z tym samym priorytetem co wątek główny / ui, rywalizując z metodami cyklu życia i szybkościami klatek ui (uważaj na ostrzeżenia w dzienniku choreografa). - Stevie
@Stevie, jak ustawić priorytet? ani runnble, ani executorService nie mają takiej metody ustawiającej - J. K.
@ J.K. Podaj swój ExecutorService z niestandardowym ThreadFactory i wywołaj Thread.setPriority w wątku przed zwróceniem go. - Stevie
Ustawienie priorytetu dla czegoś, co jest przeznaczone do wykonania połączenia sieciowego (które będzie blokować) wydaje się przesadne. - john16384


Nie możesz wykonać sieci I / O na wątku interfejsu użytkownika Plaster miodu. Technicznie, to jest możliwe w starszych wersjach Androida, ale jest to bardzo zły pomysł, ponieważ spowoduje to, że Twoja aplikacja przestanie odpowiadać i może spowodować, że system operacyjny zabije twoją aplikację za złe zachowanie. Będziesz musiał uruchomić proces w tle lub użyć AsyncTask, aby wykonać transakcję sieciową w wątku w tle.

Jest artykuł o Bezbolesne gwintowanie na stronie dla programistów Androida, która jest dobrym wprowadzeniem do tego, i dostarczy ci znacznie lepszej głębi odpowiedzi, niż może być realistycznie podana tutaj.


124
2018-06-14 12:07





Przyjęta odpowiedź ma pewne istotne minusy. Nie zaleca się używania AsyncTask do tworzenia sieci, chyba że Ty naprawdę wiesz, co robisz. Niektóre z wad obejmują:

  • AsyncTask tworzone jako niestatyczne klasy wewnętrzne mają niejawne odwołanie do otaczającego obiektu Activity, jego kontekstu i całej hierarchii widoku utworzonej przez tę aktywność. Odwołanie to zapobiega gromadzeniu śmieci w działaniu do czasu zakończenia pracy w tle programu AsyncTask. Jeśli połączenie użytkownika jest wolne i / lub pobieranie jest duże, te krótkoterminowe wycieki pamięci mogą stać się problemem - na przykład, jeśli orientacja zmienia się kilka razy (i nie anulujesz wykonywania zadań) lub użytkownik nawiguje z dala od działalności.
  • AsyncTask ma inną charakterystykę wykonania w zależności od platformy, na której wykonuje: przed AsyncTasks poziomu API 4 wykonać szeregowo na pojedynczym wątku tła; od API poziomu 4 do API poziomu 10, AsyncTasks wykonać na puli do 128 wątków; od poziomu interfejsu API 11 AsyncTask jest wykonywany szeregowo na jednym wątku tła (chyba że używa się przeciążonego executeOnExecutormetodę i dostarczyć alternatywnego executora). Kod, który działa poprawnie, gdy jest uruchamiany seryjnie w systemie ICS, może zostać przerwany, gdy jest wykonywany jednocześnie na Gingerbread, powiedzmy, jeśli masz niezamierzone zależności między kolejnością wykonania.

Jeśli chcesz uniknąć wycieków pamięci krótkoterminowej, mieć dobrze zdefiniowaną charakterystykę wykonywania na wszystkich platformach i mieć bazę do zbudowania naprawdę niezawodnej obsługi sieci, możesz rozważyć:

  1. Korzystając z biblioteki, która wykonuje to za ciebie przyjemnie - jest dobre porównanie bibliotek sieciowych w to pytanie, lub
  2. Używać Service lub IntentService zamiast tego, być może z PendingIntent aby zwrócić wynik za pośrednictwem działania onActivityResult metoda.

Podejście IntentService

Boki:

  • Więcej kodu i złożoności niż AsyncTask, choć nie tak bardzo, jak mogłoby się wydawać
  • Będzie kolejkować żądania i uruchamiać je na pojedynczy wątek tła. Możesz łatwo to kontrolować, zastępując IntentService z odpowiednikiem Service implementacja, może jak ten.
  • Um, nie mogę teraz myśleć o innych

Elementy z boku:

  • Unika problemów związanych z wyciekiem pamięci krótkotrwałej
  • Jeśli twoja aktywność uruchomi się ponownie, gdy operacje sieciowe będą w trakcie lotu, może nadal otrzymywać wynik pobierania za jego pośrednictwem onActivityResult metoda
  • Lepsza platforma niż AsyncTask do budowania i ponownego wykorzystywania niezawodnego kodu sieciowego. Przykład: jeśli musisz zrobić ważny upload, możesz to zrobić AsyncTask w Activity, ale jeśli kontekst użytkownika - wyłączy się z aplikacji, aby wykonać połączenie telefoniczne, system może zabić aplikację przed zakończeniem przesyłania. To jest mniej prawdopodobne zabić aplikację aktywną Service.
  • Jeśli używasz swojej własnej równoczesnej wersji IntentService (podobnie jak ten, który podałem powyżej) można kontrolować poziom współbieżności za pośrednictwem Executor.

Podsumowanie wdrożenia

Możesz wdrożyć IntentService szybkie pobieranie pobrań z jednego wątku tła.

Krok 1: Utwórz IntentService aby pobrać. Możesz powiedzieć, co pobrać przez Intent dodatkowe i przekazać PendingIntent użyć, aby zwrócić wynik do 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);
        }
    }
}

Krok 2: Zarejestruj usługę w manifeście:

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

Krok 3: Wywołaj usługę z działania, przekazując obiekt PendingResult, którego Usługa użyje do zwrócenia wyniku:

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

Krok 4: Radzić sobie z wynikiem 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);
}

Dostępny jest projekt Github zawierający kompletny działający projekt Android-Studio / gradle tutaj.


118
2018-01-22 13:17



IntentService jest właściwym sposobem, aby to zrobić, nie wykorzeniając, ponieważ AsyncTask jest dokładnie taki sposób, aby tego nie robić. - Brill Pappin
@BrillPappin Prawie całkowicie się zgadzam i zmieniłem treść, aby podkreślić wady AsyncTask. (Nadal uważam, że istnieje bardzo mały liczba przypadków, w których - jeśli naprawdę wiesz, co robisz - to moc być w porządku, aby używać AsyncTask, ale zaakceptowana odpowiedź nie wskazuje żadnych wad i jest zbyt popularna dla dobra Androida). - Stevie
Czy faktycznie potrzebujesz IllustrativeRSS? Co jeśli nie pracujesz z materiałem RSS? - Cullub


  1. Nie używaj strictMode (tylko w trybie debugowania)
  2. Nie zmieniaj wersji SDK
  3. Nie używaj oddzielnego wątku

Użyj usługi lub AsyncTask

Zobacz także pytanie o przepełnienie stosu:

android.os.NetworkOnMainThreadException wysyłanie wiadomości e-mail z systemu Android


59
2017-08-18 09:18



Być może warto podkreślić, że jeśli korzystasz z usługi, nadal musisz utworzyć oddzielny wątek - wywołania serwisowe działają w głównym wątku. Z kolei IntentService uruchamia swoją metodę onHandleIntent na wątku tła. - Stevie
nie powinieneś używać AsyncTask do długich operacji! Wytyczne określają od 2 do 3 sekund maks. - Dage


Wykonaj działania sieciowe w innym wątku

Na przykład:

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

I dodaj to do AndroidManifest.xml

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

51
2017-12-24 14:33



Ale jak możemy się dowiedzieć, kiedy wątek się kończy, abyśmy mogli wykonać następny zestaw zadań w wątku UI? AsyncTask zapewnia taką możliwość. Czy istnieje sposób, aby zrobić to samo za pomocą działających wątków? - Piyush Soni
Przetworzy twój kod krok po kroku, więc na końcu kodu jest zakończony, musisz użyć handler'a z powrotem do wątku UI - henry4343
mobileorchard.com/... - henry4343
Możesz użyć zadania async lub intent service, ponieważ jest to wykonywane w wątku roboczym. - Chetan Chaudhari


Wyłącz tryb ścisły, używając następującego kodu:

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

To nie jest zalecane: Użyj AsyncTaskberło.

Pełny kod dla obu metod


46
2017-10-24 07:10



Tak, byłby błąd ANR. oznacza, że ​​aplikacja nie odpowiada w ciągu 5 sekund. - Muhammad Mubashir
To naprawdę zła odpowiedź. Nie powinieneś zmieniać polityki wątku, ale napisać lepszy kod: nie rób operacji sieciowych na głównym wątku! - shkschneider
Nie jest dobre, ale przydatne do POC / debugowania - hB0
@Sandeep Ty i inni widzowie również powinni to przeczytać. stackoverflow.com/a/18335031/3470479 - Prakhar1001


Operacje oparte na sieci nie mogą być uruchamiane w głównym wątku. Musisz uruchomić wszystkie zadania sieciowe w wątku podrzędnym lub zaimplementować AsyncTask.

W ten sposób uruchamiasz zadanie w wątku podrzędnym:

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

39
2018-01-14 06:14



Anonimowy Runnable NIE jest najlepszym sposobem, ponieważ zawiera niejawne odwołanie do klasy otaczającej i uniemożliwia jej edycję GC, dopóki wątek nie zostanie ukończony! Również ten wątek będzie działał w takim samym Priorytecie jak wątek główny / US, rywalizując z metodami cyklu życia i szybkościami ramek interfejsu użytkownika! - Yousha Aleayoub


Umieść swój kod w środku:

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

Lub:

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
    }
}

37
2017-07-28 10:43



Drugi będzie najlepszy niż pierwszy dla api powyżej 11 - Rohit Goswami
przeczytaj komentarz pod: stackoverflow.com/a/21107128/1429432 - Yousha Aleayoub
Asynchronizacja Praca w większości przypadków - Ajay Pandya