Jak naprawić „android.os.NetworkOnMainThreadException”?


2393

Wystąpił błąd podczas uruchamiania 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?


131
Przeczytaj ten post na blogu w NetworkOnMainThreadException, aby uzyskać więcej informacji. Wyjaśnia, dlaczego tak się dzieje na Androidzie 3.0 i nowszych.
Adrian Monk

6
Aby być na dobrej drodze, najpierw przeczytaj o żądaniach sieciowych w Androidzie, a następnie poleciłbym studiowanie „Volley”.
Anuj Sharma

3
Istnieje wiele alternatywnych bibliotek, które rozwiązują ten problem. Wiele z nich znajduje się na dole tej strony . Jeśli dostaniesz więcej, bierzemy je :)
Snicolas

Musisz uruchomić działania internetowe w wątku innym niż główny (UI)
Naveed Ahmad

„Z powodu błędu w poprzednich wersjach Androida system nie oznaczył zapisu w gnieździe TCP w głównym wątku jako naruszenie trybu ścisłego. Android 7.0 naprawia ten błąd. Aplikacje, które wykazują takie zachowanie, teraz wysyłają android.os. NetworkOnMainThreadException. ” - Więc niektórzy z nas nie uderzyli tego do niedawna! developer.android.com/about/versions/nougat/…
Jay

Odpowiedzi:


2547

Ten wyjątek jest zgłaszany, gdy aplikacja próbuje wykonać operację sieciową w swoim głównym wątku. Uruchom swój kod w 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.javapliku możesz dodać ten wiersz do swojej oncreate()metody

new RetrieveFeedTask().execute(urlToRssFeed);

Nie zapomnij dodać tego do AndroidManifest.xmlpliku:

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

37
Myślę, że warto tutaj zauważyć, że powyższy fragment kodu ma być podklasą (klasą wewnętrzną), najlepiej prywatną. W ten sposób po zakończeniu AsyncTask możesz nadal manipulować wnętrznościami swojej klasy.
dyslexicanaboko

4
Właściwie zrobiłem to samo, co wspomniano powyżej, ale mam do czynienia z tym błędem java.lang.RuntimeException: Nie można utworzyć modułu obsługi w wątku, który nie wywołał Looper.prepare ()
Dhruv Tyagi,

68
To jest zła odpowiedź. Cały czas spotykam się z tym w kodzie narodowym i denerwujące jest to, że muszę to cały czas naprawiać. AsyncTask nie powinien być używany do aktywności sieciowej, ponieważ jest powiązany z aktywnością, ale nie z cyklem życia aktywności. Obracanie urządzenia z uruchomionym zadaniem spowoduje wyjątek i spowoduje awarię aplikacji. Zamiast tego użyj usługi IntentService, która usuwa dane z bazy danych sqlite.
Brill Pappin,

5
Uwaga, AsyncTask jest często używany do operacji sieciowych według aktywności, kiedy nie powinien. jego cykl życia nie jest zsynchronizowany z działaniem. Do pobierania danych należy użyć usługi IntentService i bazy danych za widokiem.
Brill Pappin,

1
@BrillPappin, FWIW, ten przewodnik dla programistów Androida używa AsyncTaski dba o zmianę konfiguracji.
HeyJude,

676

Prawie zawsze powinieneś uruchamiać operacje sieciowe na wątku lub jako zadanie asynchroniczne.

Ale tak jest możliwe, aby usunąć to ograniczenie i zastąpić domyślne zachowanie, jeśli jesteś w stanie zaakceptować konsekwencje.

Dodaj:

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

StrictMode.setThreadPolicy(policy); 

W Twojej klasie,

i

DODAJ to uprawnienie w pliku manifestu.xml Androida:    

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

Konsekwencje:

Twoja aplikacja (w obszarach nieregularnego połączenia internetowego) przestanie reagować i zostanie zablokowana, użytkownik odczuwa powolność i musi wykonać wymuszone zabójstwo, a Ty ryzykujesz, że menedżer aktywności zabije aplikację i poinformuje użytkownika, że ​​aplikacja się zatrzymała.

Android ma kilka dobrych wskazówek na temat dobrych praktyk programowania w zakresie projektowania responsywności: http://developer.android.com/reference/android/os/NetworkOnMainThreadException.html


456
To bardzo zły pomysł. rozwiązaniem jest uniknięcie IO sieci w głównym wątku (jak pokazuje zaakceptowana odpowiedź).
MByD

74
Dzięki temu ukrywasz tylko swój prawdziwy problem.
Alex

28
@TwistedUmbrella AsyncTask nie dodaje strony kodu, dodaje 6 wierszy (deklaracja klasy, nadpisywanie adnotacji, doInBackgrounddeklaracja, 2 nawiasy zamykające i wywołanie do execute()). Z drugiej strony, nawet jedno pobranie z witryny, jak wspomniałeś, powoduje znaczne opóźnienie w reakcji interfejsu użytkownika. Nie bądź leniwy.
Zoltán

19
Myślę, że jest to idealne rozwiązanie, jeśli chcesz po prostu uruchomić próbkę fragmentu kodu, aby sprawdzić, czy coś działa przed wdrożeniem właściwego AsyncTask. Dlatego głosowałem za tą odpowiedzią, chociaż, jak wszyscy mówili, nie powinno się tego robić w aplikacji produkcyjnej, a jedynie jako szybkie obejście testu deweloperskiego.
hooked82

94
Pozytywne. Ta odpowiedź jest poprawna, a dla wielu programistów, którzy nie są ani naiwni, ani głupi, ale po prostu wymagają wywołania SYNCHRONICZNEGO (tj. Musi to blokować aplikację ), jest to dokładnie to, czego potrzeba. Bardzo się cieszę, że Android domyślnie zgłasza wyjątek (IMHO to bardzo „pomocna” rzecz do zrobienia!) - ale równie chętnie mówię „dziękuję, ale - tak właśnie zamierzałem” i zastępuję to.
Adam

428

Rozwiązałem ten problem za pomocą nowego Thread.

Thread thread = new Thread(new Runnable() {

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

thread.start(); 

7
Zamiast tworzyć nowy wątek za każdym razem, gdy chcesz wykonać operację sieciową, możesz również użyć usługi jednego modułu wykonującego wątek.
Alex Lockwood

66
Proste, ale niebezpieczne. Anonimowy Runnable zawiera niejawne odniesienie do otaczającej klasy (np. Twojej Aktywności lub Fragmentu), zapobiegając gromadzeniu się śmieci do czasu zakończenia wątku. Powinieneś przynajmniej ustawić priorytet na Process.BACKGROUND, w przeciwnym razie ten wątek będzie działał z tym samym priorytetem co wątek główny / ui, walcząc z metodami cyklu życia i częstotliwościami wyświetlania interfejsu użytkownika (uważaj na ostrzeżenia w logu od choreografa).
Stevie

1
@Stevie jak ustawić priorytet? ani runnble, ani executorService nie mają takiej metody ustawiającej
JK

1
@ JK Podaj swojemu ExecutorService niestandardowy ThreadFactory i wywołaj Thread.setPriority w wątku przed jego zwróceniem.
Stevie

1
„Odradzane jest używanie wątków bezpośrednio w Androidzie. Powoduje to więcej problemów niż rozwiązuje” Czy chcesz to rozwinąć? Właściwie AsyncTask jest przestarzałe właśnie z tego powodu
Fran Marzoa

169

Przyjęta odpowiedź ma pewne wady. Nie zaleca się używania AsyncTask do pracy w sieci, chyba że naprawdę wiesz, co robisz. Niektóre wady obejmują:

  • AsyncTask utworzone jako niestatyczne klasy wewnętrzne mają niejawne odniesienie do otaczającego obiektu Activity, jego kontekstu i całej hierarchii widoku utworzonej przez to działanie. Odwołanie to zapobiega odśmiecaniu działania do czasu zakończenia pracy w tle AsyncTask. Jeśli połączenie użytkownika jest wolne i / lub pobieranie jest duże, te krótkotrwałe wycieki z pamięci mogą stać się problemem - na przykład, jeśli orientacja zmieni się kilka razy (i nie anulujesz wykonywanych zadań), lub użytkownik nawiguje od działania.
  • AsyncTask ma różne charakterystyki wykonywania w zależności od platformy, na której wykonuje: przed poziomem API 4 AsyncTask wykonuje się szeregowo w jednym wątku w tle; od poziomu API 4 do poziomu API 10, AsyncTasks wykonuje się w puli do 128 wątków; od poziomu API 11 i więcej AsyncTask wykonuje się szeregowo na pojedynczym wątku w tle (chyba że użyjesz przeciążonej executeOnExecutormetody i podasz alternatywny executor). Kod, który działa poprawnie, gdy działa szeregowo na ICS, może ulec uszkodzeniu, gdy jest wykonywany jednocześnie na Piernik, powiedzmy, jeśli masz niezamierzone zależności od kolejności wykonania.

Jeśli chcesz uniknąć krótkotrwałych wycieków pamięci, mieć dobrze zdefiniowane cechy wykonania na wszystkich platformach i mieć bazę do zbudowania naprawdę solidnej obsługi sieci, możesz rozważyć:

  1. Korzystanie z biblioteki, które wykonuje pracę miło, to dla ciebie - nie ma porównania miłe z sieci bibliotekami w tej kwestii , czy
  2. Użycie a Servicelub IntentServicezamiast, być może z a, PendingIntentaby zwrócić wynik za pomocą onActivityResultmetody działania.

Podejście IntentService

Wady:

  • 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 w jednym wątku w tle. Możesz to łatwo kontrolować, zastępując IntentServicerównoważną Serviceimplementacją, być może taką jak ta .
  • Nie mogę teraz myśleć o żadnym innym

Zalety:

  • Zapobiega problemowi z wyciekiem pamięci krótkoterminowej
  • Jeśli aktywność zostanie wznowiona, gdy operacje sieciowe odbywają się w locie, nadal można otrzymać wynik pobierania za pomocą tej onActivityResultmetody
  • Lepsza platforma niż AsyncTask do budowania i ponownego wykorzystywania niezawodnego kodu sieciowego. Przykład: jeśli trzeba zrobić ważną upload, można zrobić go od AsyncTaskw sposób Activity, ale jeśli użytkownik przełączy kontekst z aplikacją do podjęcia rozmowy telefonicznej, system może zabić aplikację zanim zakończy wysyłania. Jest mniej prawdopodobne, że zabije aplikację z aktywnym Service.
  • Jeśli korzystasz z własnej współbieżnej wersji IntentService(takiej jak ta, którą zamieściłem powyżej), możesz kontrolować poziom współbieżności za pośrednictwem Executor.

Podsumowanie wdrożenia

Możesz IntentServicedość łatwo zaimplementować opcję pobierania w jednym wątku w tle.

Krok 1: Utwórz, IntentServiceaby wykonać pobieranie. Możesz powiedzieć, co pobrać za pośrednictwem Intentdodatkowych, i przekazać to, PendingIntentaby 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: Obsługa wyniku w funkcji 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);
}

Projekt Github zawierający kompletny działający projekt Android-Studio / Gradle jest dostępny tutaj .


IntentService to właściwy sposób, aby to zrobić, a nie wyrywanie, ponieważ AsyncTask jest dokładnie sposobem, aby tego nie robić.
Brill Pappin,

3
@BrillPappin Prawie całkowicie się zgadzam i przeredagowałem, aby podkreślić wady AsyncTask. (Nadal uważam, że istnieje bardzo mała liczba przypadków, w których - jeśli naprawdę wiesz, co robisz - użycie AsyncTask może być w porządku, ale zaakceptowana odpowiedź nie wskazuje na żadne wady i jest zbyt popularna dla dobrych Androida).
Stevie,

1
Czy naprawdę potrzebujesz IllustrativeRSS? Co jeśli nie pracujesz z materiałami RSS?
Cullub

Moim zdaniem Google powinien zmienić swoją złą implementację odśmiecania, zamiast postawić berdona po stronie programistów. To jest odpowiedzialność systemu operacyjnego. Jeśli programista użyje IntentService lub Service do wykonania zadania z powodu podstawowego problemu we wdrażaniu Androida, po kilku latach Google powie, że IntentService jest również złą praktyką i zasugeruje coś innego. I ta historia trwa ... Więc programiści Google powinni rozwiązywać problemy z zarządzaniem błędną pamięcią Androida, a nie programistami.
saeed khalafinejad

Powiem tylko: ta odpowiedź wydaje się bardziej sposobem na udostępnienie projektu niż lepszą alternatywą AsyncTask. Błąd miał na celu powstrzymanie deweloperów przed opóźnieniem interfejsu użytkownika, niekoniecznie zachęcając ich do ryzykowania bezpieczeństwa za pomocą szeregu intencji / usług.
Opuszczony

144

Nie można wykonać operacji we / wy sieci w wątku interfejsu użytkownika w programie Honeycomb . Technicznie jest to możliwe we wcześniejszych wersjach Androida, ale jest to naprawdę zły pomysł, ponieważ spowoduje, że Twoja aplikacja przestanie odpowiadać i może spowodować, że system operacyjny zabije twoją aplikację za złe zachowanie. Musisz uruchomić proces w tle lub użyć AsyncTask, aby wykonać transakcję sieciową w wątku w tle.

Na stronie dewelopera Androida jest artykuł o Bezbolesnym wątku, który jest dobrym wstępem do tego i zapewni o wiele lepszą głębię odpowiedzi, niż można realistycznie podać tutaj.


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

Użyj usługi lub AsyncTask

Zobacz także pytanie Przepełnienie stosu:

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


8
Być może warto podkreślić, że jeśli korzystasz z usługi, nadal musisz utworzyć osobny wątek - wywołania zwrotne usługi są uruchamiane w głównym wątku. Z drugiej strony IntentService uruchamia swoją metodę onHandleIntent w wątku w tle.
Stevie

nie powinieneś używać AsyncTask do długotrwałych operacji! Wytyczne określają maksymalnie 2 do 3 sekund.
Dage

76

Wykonaj akcje 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"/>

5
Ale jak możemy dowiedzieć się, kiedy wątek się w tym skończy, abyśmy mogli wykonać następny zestaw zadań w wątku interfejsu użytkownika? AsyncTask zapewnia taką możliwość. Czy istnieje sposób, aby zrobić to samo za pomocą uruchamialnych wątków?
Piyush Soni

3
Przetwarza kod krok po kroku, więc na końcu kodu jest zakończony, musisz użyć procedury obsługi z powrotem do wątku interfejsu użytkownika
henry4343

1
Możesz użyć zadania asynchronicznego lub usługi zamiaru, ponieważ jest ona wykonywana w wątku roboczym.
Chetan Chaudhari,

63

Wyłącz tryb ścisły za pomocą następującego kodu:

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

Nie jest to zalecane : użyjAsyncTask interfejsu.

Pełny kod dla obu metod


2
Tak, wystąpił błąd ANR. oznacza, że ​​aplikacja nie odpowiada przez 5 sekund.
Muhammad Mubashir

11
To jest naprawdę zła odpowiedź. Nie powinieneś zmieniać zasad wątku, ale pisać lepszy kod: nie wykonuj operacji sieciowych na głównym wątku!
shkschneider

@ Sandeep Ty i inni widzowie też powinniście to przeczytać. stackoverflow.com/a/18335031/3470479
Prakhar1001 15.09.16

54

Operacje sieciowe nie mogą być uruchamiane w głównym wątku. Musisz uruchomić wszystkie zadania sieciowe w wątku potomnym lub zaimplementować AsyncTask.

Oto jak uruchamiasz zadanie w wątku potomnym:

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

Anonimowy Runnable NIE jest najlepszym sposobem, ponieważ zawiera niejawne odniesienie do otaczającej klasy i uniemożliwia edycję GC aż do zakończenia wątku! Również ten wątek będzie działał z takim samym priorytetem jak wątek główny / amerykański, walcząc z metodami cyklu życia i szybkościami wyświetlania interfejsu użytkownika!
Yousha Aleayoub,

49

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

Drugi będzie najlepszy niż pierwszy dla api powyżej 11
Rohit Goswami

46

Dzieje się tak w Androidzie 3.0 i nowszych. W Androidzie 3.0 i nowszych ograniczyły korzystanie z operacji sieciowych (funkcji dostępu do Internetu) przed uruchomieniem w głównym wątku / wątku interfejsu użytkownika (co pojawia się podczas tworzenia i wznawiania metod w działaniu).

Ma to na celu zachęcenie do używania osobnych wątków do operacji sieciowych. Zobacz AsyncTask, aby uzyskać więcej informacji na temat wykonywania działań sieciowych we właściwy sposób.


46

Korzystanie z adnotacji na Androida jest opcją. Umożliwi to po prostu uruchomienie dowolnej metody w wątku w tle:

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

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

Należy pamiętać, że chociaż zapewnia zalety prostoty i czytelności, ma swoje wady.


6
@Gavriel tworzy duplikaty wszystkiego, co adnotujesz, niezależnie od tego, czy jest to metoda, aktywność, fragment, singleton itp., Więc kodu jest dwa razy więcej i jego skompilowanie zajmuje więcej czasu. Może mieć również pewne problemy z powodu błędów w bibliotece. Debugowanie i znajdowanie błędów stałoby się trudniejsze.
Oleksiy

43

Błąd wynika z wykonywania długotrwałych operacji w głównym wątku. Problem można łatwo rozwiązać za pomocą AsynTask lub Thread . Możesz pobrać tę bibliotekę AsyncHTTPClient dla lepszej obsługi.

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

42

Nie powinieneś wykonywać żadnych czasochłonnych zadań w głównym wątku (wątku interfejsu użytkownika), takich jak operacje sieciowe, operacje we / wy pliku lub operacje na bazie danych SQLite. W przypadku tego rodzaju operacji należy utworzyć wątek roboczy, ale problem polega na tym, że nie można bezpośrednio wykonać żadnej operacji związanej z interfejsem użytkownika z wątku roboczego. W tym celu musisz użyć Handleri przekazać Message.

Aby uprościć wszystkie te rzeczy, Android zapewnia różne sposoby, jak AsyncTask, AsyncTaskLoader, CursorLoaderlub IntentService. Możesz więc użyć dowolnego z nich zgodnie ze swoimi wymaganiami.


40

Najlepsza odpowiedź spektom działa idealnie.

Jeśli piszesz AsyncTaskinline, a nie rozszerzasz jako klasę, a ponadto, jeśli istnieje potrzeba uzyskania odpowiedzi z AsyncTask, możesz użyć get()metody opisanej poniżej.

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

(Z jego przykładu.)


5
używanie get()jest złym pomysłem ... powoduje, że AsyncTask ponownie „synchronizuje”
Selvin

Czy jest na to lepsze wyjście? @Selvin
sivag1

2
Myślę, że możesz poinformować główny wątek o wyniku, na przykład wysłać transmisję do głównego wątku, w tym wynik.
Chine Gary


28

Dla mnie było to tak:

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

Urządzenie, na którym testowałem moją aplikację, to 4.1.2, czyli SDK w wersji 16!

Upewnij się, że wersja docelowa jest taka sama jak Twoja biblioteka docelowa Androida. Jeśli nie masz pewności, jaka jest twoja biblioteka docelowa, kliknij prawym przyciskiem myszy swój Projekt -> Ścieżka kompilacji -> Android i powinna być zaznaczona.

Ponadto, jak wspomnieli inni, obejmują prawidłowe uprawnienia dostępu do Internetu:

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

11
Pozwól, że wyjaśnię ci, co tutaj robisz: NetworkOnMainThreadExceptionczy Strażnik mówi ci: nie strzelaj do własnej stopy ... Twoim rozwiązaniem jest: wróćmy do przeszłości, gdy nie było Strażnika - teraz mogę strzelać do mojego pieszo swobodnie
Selvin

1
Przyjąłem to podejście i nie miałem żadnych problemów. Strażnik jest czasem zbyt wybredny.
FractalBob

25

Wykorzystaj to w swojej działalności

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

23

Aby wyrazić coś wprost:

Główny wątek to w zasadzie wątek interfejsu użytkownika.

Mówienie, że nie można wykonywać operacji sieciowych w głównym wątku oznacza, że ​​nie można wykonywać operacji sieciowych w wątku interfejsu użytkownika, co oznacza, że nie można również wykonywać operacji sieciowych w *runOnUiThread(new Runnable() { ... }*bloku wewnątrz innego wątku.

(Właśnie miałem długi moment, kiedy próbowałem zrozumieć, dlaczego dostaję ten błąd gdzieś indziej niż mój główny wątek. Właśnie dlatego; ten wątek pomógł; i mam nadzieję, że ten komentarz pomoże komuś innemu.)


22

Ten wyjątek występuje z powodu każdego ciężkiego zadania wykonanego w głównym wątku, jeśli wykonanie tego zadania zajmuje zbyt dużo czasu .

Aby tego uniknąć, możemy sobie z tym poradzić przy użyciu wątków lub programów wykonawczych

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

18

Istnieje wiele wspaniałych odpowiedzi na to pytanie, ale od czasu opublikowania tych odpowiedzi pojawiło się wiele świetnych bibliotek. Jest to przeznaczone jako rodzaj przewodnika dla początkujących.

Omówię kilka przypadków użycia do wykonywania operacji sieciowych i do roztworu lub dwa dla każdego.

ReST przez HTTP

Zazwyczaj Json może być XML lub czymś innym

Pełny dostęp do interfejsu API

Załóżmy, że piszesz aplikację, która pozwala użytkownikom śledzić ceny akcji, stopy procentowe i kursy walut. Znajdziesz Json API, który wygląda mniej więcej tak:

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)

Modernizacja z Square

Jest to doskonały wybór dla interfejsu API z wieloma punktami końcowymi i pozwala zadeklarować punkty końcowe ReST zamiast kodować je indywidualnie, jak w przypadku innych bibliotek, takich jak ion lub Volley. (strona internetowa: http://square.github.io/retrofit/ )

Jak korzystać z interfejsu API finansów?

build.gradle

Dodaj następujące wiersze do poziomu modułu 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);
}

FinanseApiBuilder

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

Fragment fragmentu finansów

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

Jeśli Twój interfejs API wymaga wysłania klucza API lub innego nagłówka, takiego jak token użytkownika itp., Retrofit ułatwia to (zobacz tę niesamowitą odpowiedź, aby uzyskać szczegółowe informacje: https://stackoverflow.com/a/42899766/1024412 ).

Jednorazowy dostęp do interfejsu API ReST

Załóżmy, że budujesz aplikację „nastrojowa pogoda”, która sprawdza lokalizację GPS użytkowników, sprawdza aktualną temperaturę w tym obszarze i informuje ich o nastroju. Ten typ aplikacji nie musi deklarować punktów końcowych interfejsu API; po prostu musi mieć dostęp do jednego punktu końcowego interfejsu API.

Jon

To świetna biblioteka dla tego typu dostępu.

Przeczytaj wspaniałą odpowiedź msysmilu ( https://stackoverflow.com/a/28559884/1024412 )

Załaduj obrazy przez HTTP

Salwa

Volley może być również użyty dla interfejsów API ReST, ale z powodu bardziej skomplikowanej konfiguracji wolę używać Retrofit z Square jak wyżej ( http://square.github.io/retrofit/ )

Załóżmy, że tworzysz aplikację społecznościową i chcesz załadować zdjęcia profilowe znajomych.

build.gradle

Dodaj tę linię do poziomu modułu buid.gradle:

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

ImageFetch.java

Siatkówka wymaga większej konfiguracji niż modernizacja. Będziesz musiał stworzyć taką klasę, aby skonfigurować RequestQueue, ImageLoader i ImageCache, ale nie jest tak źle:

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

Dodaj następujące elementy do pliku XML układu, aby dodać obraz:

<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

Dodaj następujący kod do metody onCreate (fragment, działanie) lub konstruktora (okno dialogowe):

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

Picasso

Kolejna doskonała biblioteka z Square. Proszę zobaczyć na stronie kilka świetnych przykładów: http://square.github.io/picasso/


16

W prostych słowach

NIE PRACUJ W SIECI W NICI UI

Na przykład, jeśli wykonasz żądanie HTTP, jest to działanie sieciowe.

Rozwiązanie:

  1. Musisz utworzyć nowy wątek
  2. Lub użyj klasy AsyncTask

Droga:

Umieść wszystkie swoje prace w środku

  1. run() metoda nowego wątku
  2. Lub doInBackground() metoda klasy AsyncTask.

Ale:

Gdy otrzymasz coś z odpowiedzi sieciowej i chcesz pokazać go w swoim widoku (np. Komunikat odpowiedzi wyświetlacza w TextView), musisz wrócić do wątku interfejsu użytkownika .

Jeśli tego nie zrobisz, dostaniesz ViewRootImpl$CalledFromWrongThreadException.

Jak?

  1. Podczas korzystania z AsyncTask zaktualizuj widok z onPostExecute()metody
  2. Lub wywołaj runOnUiThread()metodę i aktualizuj widok wewnątrz run()metody.

12

Możesz przenieść część kodu do innego wątku, aby odciążyć main threadi uniknąć uzyskania ANR , NetworkOnMainThreadException , IllegalStateException (np. Nie można uzyskać dostępu do bazy danych w głównym wątku, ponieważ może to potencjalnie zablokować interfejs użytkownika na długi okres czasu).

Istnieje kilka metod, które należy wybrać w zależności od sytuacji

Wątek Java lub Android HandlerThread

Wątki Java są używane jednorazowo i giną po wykonaniu metody uruchamiania.

HandlerThread to przydatna klasa do rozpoczynania nowego wątku, który ma pętlę.

AsyncTask

AsyncTask został zaprojektowany jako klasa pomocnicza wokół Thread and Handler i nie stanowi ogólnego szkieletu wątków. AsyncTasks najlepiej powinien być używany do krótkich operacji (najwyżej kilka sekund). Jeśli chcesz, aby wątki działały przez długi czas, zdecydowanie zalecamy korzystanie z różnych interfejsów API dostarczanych przez pakiet java.util.concurrent, takich jak Executor , ThreadPoolExecutor i FutureTask .

Implementacja puli wątków ThreadPoolExecutor , ScheduledThreadPoolExecutor ...

Klasa ThreadPoolExecutor, która implementuje ExecutorService, która zapewnia dokładną kontrolę w puli wątków (np. Wielkość puli rdzeniowej, maksymalny rozmiar puli, utrzymanie czasu życia itp.)

ScheduledThreadPoolExecutor - klasa rozszerzająca ThreadPoolExecutor. Może planować zadania po określonym czasie lub okresowo.

FutureTask

FutureTask wykonuje przetwarzanie asynchroniczne, jednak jeśli wynik nie jest jeszcze gotowy lub przetwarzanie nie zostało zakończone, wywołanie get () zablokuje wątek

AsyncTaskLoaders

AsyncTaskLoaders, ponieważ rozwiązują wiele problemów związanych z AsyncTask

IntentService

To jest wybór defacto dla długotrwałego przetwarzania na Androidzie, dobrym przykładem byłoby przesyłanie lub pobieranie dużych plików. Przesyłanie i pobieranie może być kontynuowane, nawet jeśli użytkownik zamknie aplikację, a na pewno nie chcesz blokować możliwości korzystania z aplikacji podczas wykonywania tych zadań.

JobScheduler

W efekcie musisz utworzyć usługę i zadanie za pomocą JobInfo.Builder, które określa kryteria uruchomienia usługi.

RxJava

Biblioteka do tworzenia programów asynchronicznych i opartych na zdarzeniach przy użyciu obserwowalnych sekwencji.

Coroutines (Kotlin)

Główną jego zaletą jest to, że kod asynchroniczny wygląda tak samo jak synchroniczny

Czytaj więcej tutaj , tutaj , tutaj , tutaj


Pracowałem dla mnie ... Używałem AsyncTask intensywnie, ale kiedy jedno zadanie jest uruchomione, inne będzie czekać. Chcę to rozwiązać. Teraz współpracuje z executeonexecutor. Zobaczmy, jak będzie się zachowywał na urządzeniach o niskiej pamięci.
Suraj Shingade,

Proszę spojrzeć na metodę: asyncTask.executeOnExecutor (AsyncTask.THREAD_POOL_EXECUTOR, params); aby uruchomić zadanie jednocześnie
yoAlex5

10

Chociaż powyżej znajduje się ogromna pula rozwiązań, nikt nie wspominał com.koushikdutta.ion: https://github.com/koush/ion

Jest również asynchroniczny i bardzo prosty w użyciu:

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

10

Nowe Threadi AsyncTask zostały już wyjaśnione.

AsyncTaskidealnie nadaje się do krótkich operacji. Normalny Threadnie jest preferowany dla Androida.

Zobacz alternatywne rozwiązanie za pomocą HandlerThread i Handler

HandlerThread

Przydatna klasa do rozpoczęcia nowego wątku z chwytaczem. Looper może być następnie użyty do stworzenia klas procedur obsługi. Pamiętaj, że start()nadal trzeba się nazywać.

Treser:

Moduł obsługi umożliwia wysyłanie i przetwarzanie obiektów Message i Runnable powiązanych z MessageQueue wątku. Każda instancja modułu obsługi jest powiązana z jednym wątkiem i kolejką komunikatów tego wątku. Kiedy tworzysz nowy moduł obsługi, jest on powiązany z kolejką wątków / komunikatów tworzącego go wątku - od tego momentu będzie dostarczał komunikaty i kanały uruchomieniowe do tej kolejki komunikatów i wykonywał je, gdy wyjdą z komunikatu kolejka.

Rozwiązanie:

  1. Stwórz HandlerThread

  2. zadzwoń start()naHandlerThread

  3. Twórz Handler, pobierając LooperzHanlerThread

  4. Osadź kod związany z operacją sieci w Runnableobiekcie

  5. Prześlij Runnablezadanie doHandler

Przykładowy fragment kodu, którego adres 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);

Zalety stosowania tego podejścia:

  1. Tworzenie nowych Thread/AsyncTaskdla każdej operacji sieciowej jest kosztowne. Thread/AsyncTaskZostaną zniszczone i ponownie tworzone dla kolejnych operacji sieciowych. Ale za pomocą Handleri HandlerThreadpodejście można przesłać wiele operacji sieciowych (jako zadania Runnable) do pojedynczego HandlerThreadza pomocą Handler.

8

RxAndroidjest kolejną lepszą alternatywą dla tego problemu i oszczędza nam kłopotów z tworzeniem wątków, a następnie publikowaniem wyników w wątku interfejsu użytkownika Androida. Musimy tylko określić wątki, na których zadania muszą zostać wykonane, a wszystko jest obsługiwane wewnętrznie.

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. Przez specifiying (Schedulers.io()), RxAndroid będzie działać getFavoriteMusicShows() na innym wątku.

  2. Za pomocą AndroidSchedulers.mainThread()tego chcemy obserwować ten Obserable w wątku interfejsu użytkownika, tzn. Chcemy, aby nasze onNext()wywołanie zwrotne było wywoływane w wątku interfejsu użytkownika


8

Główny wątek jest wątkiem interfejsu użytkownika i nie można wykonać operacji w głównym wątku, która może zablokować interakcję użytkownika. Możesz to rozwiązać na dwa sposoby:

Wymuś wykonanie zadania w głównym wątku, takim jak ten

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

Lub stwórz prostą procedurę obsługi i zaktualizuj główny wątek, jeśli chcesz.

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

Aby zatrzymać wątek, użyj:

newHandler.removeCallbacks(runnable);

Aby uzyskać więcej informacji, sprawdź to: Bezbolesne nawlekanie


Dzięki. Wersja 1 pomaga przy dodawaniu jako pierwszej akcji w programie onCreate.
Ingo

7

To działa. Po prostu uprościłem odpowiedź Dr.Luiji.

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

7

W systemie Android operacji sieciowych nie można uruchamiać w głównym wątku. Do wykonywania operacji sieciowych można używać Thread, AsyncTask (zadania krótkotrwałe), Service (zadania długoterminowe).


7

Dostęp do zasobów sieciowych z głównego wątku (UI) powoduje ten wyjątek. Użyj osobnego wątku lub AsyncTask, aby uzyskać dostęp do zasobu sieciowego, aby uniknąć tego problemu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.