Android SDK AsyncTask doInBackground nie działa (podklasa)

90

Na dzień 15/2/2012 nie znalazłem jeszcze dobrego wyjaśnienia ani powodu, dla którego to nie działa. Najbliższym rozwiązaniem jest użycie tradycyjnego podejścia Thread , ale w takim razie po co dołączać klasę, która (wydaje się) nie działać w Android SDK?

Nawet TAK!

Mam podklasę AsyncTask:

// ParseListener had a callback which was called when an item was parsed in a
// RSS-xml, but as stated further down it is not used at all right now.
private class xmlAsync extends AsyncTask<String, RSSItem, Void> implements ParseListener

To jest wykonywane w ten sposób:

xmlAsync xmlThread = new xmlAsync();

xmlThread.execute("http://www.nothing.com");

Teraz ta podklasa napotkała mały błąd. Wcześniej przeprowadzał parsowanie xml, ale kiedy zauważyłem, że metoda doInBackground () nie została wywołana , usunąłem ją, linia po linii, ostatecznie kończąc na tym:

@Override
protected Void doInBackground(String... params) 
{
    Log.v(TAG, "doInBackground");
        return null;
}

Który z jakiegoś powodu nic nie zarejestrował. Jednak dodałem to:

@Override
protected void onPreExecute() 
{
        Log.v(TAG, "onPreExecute");
        super.onPreExecute();
}

Ta linia jest rzeczywiście rejestrowana podczas wykonywania wątku. W jakiś sposób wywoływana jest metoda onPreExecute (), ale nie metoda doInBackground () . Mam jednocześnie inne AsyncTask działające w tle, które działa dobrze.

Obecnie używam aplikacji na emulatorze SDK w wersji 15, Eclipse, Mac OS X 10.7.2, blisko bieguna północnego.

EDYTOWAĆ:

@Override
    protected void onProgressUpdate(RSSItem... values) {

        if(values[0] == null)
        {
                            // activity function which merely creates a dialog
            showInputError();
        }
        else
        {

            Log.v(TAG, "adding "+values[0].toString());
            _tableManager.addRSSItem(values[0]);
        }


        super.onProgressUpdate(values);
    }

_tableManager.addRSSItem () mniej więcej dodaje wiersz do bazy danych SQLiteDatabase, zainicjowany z kontekstem działania. PublishingProgress () jest wywoływana przez wywołanie zwrotne interfejsu ParseListener. Jednakże, ponieważ nie robię nawet nic poza log.v w doInBackground (), po raz pierwszy uznałem to za niepotrzebne nawet do wywoływania.

EDYCJA 2:

W porządku, żeby było jasne, to jest drugie AsyncTask, wykonujące tę samą czynność i działające idealnie.

private class dbAsync extends AsyncTask<Void, RSSItem, Void>
{
    Integer prevCount;
    boolean run;

    @Override
    protected void onPreExecute() {
        run = true;
        super.onPreExecute();
    }

    @Override
    protected Void doInBackground(Void... params) {
        // TODO Auto-generated method stub
        run = true;
        prevCount = 0;

        while(run)
        {
            ArrayList<RSSItem> items = _tableManager.getAllItems();

            if(items != null)
            {
                if(items.size() > prevCount)
                {
                    Log.v("db Thread", "Found new item(s)!");
                    prevCount = items.size();

                    RSSItem[] itemsArray = new RSSItem[items.size()];

                    publishProgress(items.toArray(itemsArray));
                }
            }               

            SystemClock.sleep(5000);
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(RSSItem... values) {

        ArrayList<RSSItem> list = new ArrayList<RSSItem>();

        for(int i = 0; i < values.length; i++)
        {
            list.add(i, values[i]);
        }

        setItemsAndUpdateList(list);

        super.onProgressUpdate(values);
    }

    @Override
    protected void onCancelled() {
        run = false;

        super.onCancelled();
    }
}

EDYCJA 3:

Wzdycham, przepraszam, źle mi zadaje pytania Ale tutaj jest inicjalizacja zadań.

xmlAsync _xmlParseThread;
dbAsync _dbLookup;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

_dbLookup = new dbAsync();
_dbLookup.execute();

_xmlParseThread = new xmlAsync();       
_xmlParseThread.execute("http://www.nothing.com", null);
}
SeruK
źródło
czy to możliwe, że działanie kończy się przed zakończeniem zadania?
Paul Nikonowicz
Wysoce nieprawdopodobne. W takim przypadku mój drugi wątek nie działałby, prawda? W tej chwili mam tylko jedno zajęcie.
SeruK
2
Moja aplikacja ma ten sam problem - funkcja doInBackground nie jest wywoływana lub wywoływana z bardzo dużym opóźnieniem. Oto moja ograniczona obserwacja: dokładnie ten sam kod działa bezbłędnie na smartfonie z Androidem 2.3.3 i emulatorze z Androidem 2.3.3, ale ma ten problem na tablecie z Androidem 4.0.3 i kilku emulatorach Androida 4.xx. Bardzo kuszące jest stwierdzenie, że problem ten pojawił się w nowszych wersjach Androida.
Hong
Przepraszam, ale zapomniałem wspomnieć, że ten problem występuje tylko w przypadku drugiego AsyncTask działania. Pierwsze AsyncTask zawsze działa dobrze.
Hong
Hong, czy wypróbowałeś odpowiedź Matthieu? W większości wypadłem z bankomatu gier na Androida i nie pracowałem z nim przez jakiś czas, więc nie mogę powiedzieć, czy jego odpowiedzi faktycznie działają. Jeśli nie, to może źle z mojej strony przyjęłam jego odpowiedź ...
SeruK,

Odpowiedzi:

107

Rozwiązanie Matthieu będzie działać dobrze w większości przypadków, ale niektórzy mogą napotkać problem; chyba że kopie w wielu linkach podanych tutaj lub z sieci, jak wyjaśnienie Andersa Göranssona . Próbuję podsumować kilka innych odczytów tutaj i szybko wyjaśnić rozwiązanie, jeśli executeOnExecutor nadal działa w jednym wątku ...

Zachowanie AsyncTask().execute();zmieniło się w wersjach Androida. Przed Donutem (Android: 1.6 API: 4) zadania były wykonywane szeregowo, od Donut do Gingerbread (Android: 2.3 API: 9) zadania wykonywane równolegle; ponieważ wykonanie Honeycomb (Android: 3.0 API: 11) zostało przełączone z powrotem na sekwencyjne; dodano AsyncTask().executeOnExecutor(Executor)jednak nową metodę do wykonywania równoległego.

W przetwarzaniu sekwencyjnym wszystkie zadania Async są uruchamiane w jednym wątku i dlatego muszą czekać na zakończenie poprzedniego zadania. Jeśli chcesz wykonać kod natychmiast, potrzebujesz, aby zadania były przetwarzane równolegle w oddzielnych wątkach.

W przypadku AsyncTask seryjne wykonywanie nie jest dostępne między wersjami Donut i Honeycomb, podczas gdy wykonywanie równoległe nie jest dostępne przed Donut.

W przypadku przetwarzania równoległego po Donut: sprawdź wersję kompilacji i na jej podstawie użyj metody .execute () lub .executeOnExecutor (). Poniższy kod może pomóc ...

AsyncTask<Void,Void,Void> myTask = new AsyncTask<Void,Void,Void>() { ... }; // ... your AsyncTask code goes here
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB)
    myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
    myTask.execute();

NOTE:Funkcja .executeOnExecutor()sprawdza, czy targetSdkVersionprojekt jest mniejszy lub równy HONEYCOMB_MR1(Android: 2.1 API: 7), a następnie wymusza wykonanie programu THREAD_POOL_EXECUTOR(który uruchamia zadania sekwencyjnie w post Honeycomb).
Jeśli nie zdefiniowałeś a, targetSdkVersionto minSdkVersionjest automatycznie traktowane jako targetSdkVersion.
Dlatego do równoległego uruchamiania AsyncTask na post Honeycomb nie można pozostawić targetSdkVersionpustego.

Nashe
źródło
1
Bardzo dobra odpowiedź. Chociaż Matthieu się nie myli, akceptuję to, ponieważ dodajesz kilka ważnych informacji.
SeruK
@Nashe Thanks a lot. To naprawdę bardzo pomocne. Walczyłem z tym samym problemem przez 3 dni.
Uratowałem mój dzień! Chciałbym, żeby ta odpowiedź była łatwiejsza do znalezienia.
zjk
4
Hej @ Nashe, mój problem jest trochę niezręczny. Wcześniej korzystałem z metody .execute () na AsyncTask i kod działał doskonale. Ale dzisiaj mam problem - formant nie przechodzi do metody doInBackground (). Chociaż rozwiązanie, które podałeś, działa, jestem zdziwiony, jak działało wcześniej bez rozwiązania. Używam tego samego zestawu urządzeń wcześniej i teraz.
Ravi Sisodia
Dlaczego tak powinno być? Dlaczego nie działa zgodnie z oczekiwaniami? :(
Nikolay R
160

Powinieneś sprawdzić tę odpowiedź: https://stackoverflow.com/a/10406894/347565 i link do grup google, które zawiera.

Miałem podobny problem jak Ty, nadal nie wiadomo, dlaczego nie działa, ale zmieniłem kod w ten sposób i problem zniknął:

ASyncTask<Void,Void,Void> my_task = new ASyncTask<Void,Void,Void>() { ... };
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
    my_task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
else
    my_task.execute((Void[])null);
Matthieu
źródło
Byłem bardzo zły patrząc wstecz na ten post; Już dawno odszedłem od projektu. Po prostu przyjmuję to jako odpowiedź, ponieważ ludzie wydają się mówić, że działa.
SeruK
to działało dla mnie właśnie teraz, ale chciałbym zrozumieć, dlaczego. działało dobrze z wykonaniem, zanim zostało zatrzymane. jedną rzeczą, którą robię, jest rozpoczęcie nowego asynchronicznego zadania w onPostExecute tego samego asynctask (tj. nazywam to rekurencyjnie). Może jest to związane z problemem?
steveh
Myślę, że to powinno dać ci wszystkie potrzebne wyjaśnienia: commonsware.com/blog/2012/04/20/…
Matthieu,
1
@Matthieu: Sir, naprawdę nie mogę ci wystarczająco podziękować !!! Waliłem się w głowę nad tym problemem przez wiele godzin, a twoje rozwiązanie sprawiło, że wszystko działało jak urok! Bardzo dziękuję za fantastyczną odpowiedź. Chciałbym móc dać więcej niż jeden głos za!
Swayam
9

Możesz to zrobić na dwa sposoby:

Sposób 1 :

if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB) // Above Api Level 13
  {
      asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  }
else // Below Api Level 13
  {
      asyncTask.execute();
  }

W przypadku, gdy sposób 1 nie działa, wypróbuj sposób 2 .

Sposób 2 :

int mCorePoolSize = 60;
int mMaximumPoolSize = 80;
int mKeepAliveTime = 10;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(mMaximumPoolSize);
Executor mCustomThreadPoolExecutor = new ThreadPoolExecutor(mCorePoolSize, mMaximumPoolSize, mKeepAliveTime, TimeUnit.SECONDS, workQueue);
asyncTask.executeOnExecutor(mCustomThreadPoolExecutor);

Mam nadzieję, że to ci pomoże.

Hiren Patel
źródło
super stary, działa, ale kolejne kolejne zadania Async, które używają AsyncTask.THREAD_POOL_EXECUTOR kończą się niepowodzeniem, więc muszę zmienić z CustomExecutor w całym projekcie :(
kumar
@vinu, sugeruję użycie wspólnego zadania asynchronicznego i wspólnej metody wykonywania AsyncTask. Mam nadzieję, że to ci pomoże.
Hiren Patel
2
Hej, to mi bardzo pomogło! Dzięki!
Justin Ebby
6

Miałem ten sam problem: nie mogę wykonać drugiego AsyncTask po wywołaniu „execute” na pierwszym: funkcja doInBackground jest wywoływana tylko dla pierwszego.

Aby odpowiedzieć, dlaczego tak się dzieje, zaznacz tę odpowiedź (różne zachowanie w zależności od SDK)

Jednak w twoim przypadku tę przeszkodę można ominąć, używając executeOnExecutor (dostępne od wersji 3.0 działało dla mnie przy użyciu 4.0.3), ale uważaj na ograniczenia rozmiaru puli wątków i kolejkowania.

Czy możesz spróbować czegoś takiego:

xmlAsync _xmlParseThread;
dbAsync _dbLookup;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

_dbLookup = new dbAsync();
_dbLookup.execute();

_xmlParseThread = new xmlAsync();       
_xmlParseThread.executeOnExecutor(_dbLookup.THREAD_POOL_EXECUTOR
 ,"http://www.nothing.com", null);
}

W przypadku pytania o aktualizację: zostało to wyjaśnione w dokumentach Zasadniczo, aby uniknąć wszystkich problemów, które mogą wynikać z wielowątkowości, takich jak interferencja ...

code7amza
źródło
5

Jedną rzeczą, którą chciałbym wiedzieć i która może faktycznie rozwiązać Twój problem, jest to, gdzie tworzysz wystąpienie swojej klasy i wywołujesz metodę execute ()? Jeśli przeczytasz dokumentację AsyncTask, obie te operacje muszą zostać wykonane w głównym wątku interfejsu użytkownika. Jeśli tworzysz obiekt i wywołujesz execute z innego wątku, to onPreExecute może odpalić, nie jestem tutaj w 100% pewien, ale wątek w tle nie zostanie utworzony i wykonany.

Jeśli tworzysz wystąpienie swojego AsyncTask z wątku w tle lub inną operację, która nie jest wykonywana w głównym wątku interfejsu użytkownika, możesz rozważyć użycie metody: Activity.runOnUiThread (Runnable)

Aby wywołać tę metodę, potrzebujesz dostępu do wystąpienia uruchomionego działania, ale umożliwi to uruchomienie kodu w wątku interfejsu użytkownika z innego kodu, który nie jest uruchomiony w wątku interfejsu użytkownika.

Mam nadzieję, że to ma sens. Daj mi znać, jeśli mogę więcej pomóc.

David

David C. Sainte-Claire
źródło
dodając do swojej odpowiedzi ten wątek zawiera ciekawą odpowiedź stackoverflow.com/questions/4080808/… .
manjusg
Dzięki za świetną odpowiedź! Podkręciłem to, ponieważ myślę, że może to być częsty problem dla początkujących AsyncTask. Niestety, nie jest to do końca dobra odpowiedź na ten problem. Obie klasy są tworzone w ramach onCreate () działania działającego w głównym wątku interfejsu użytkownika. W tym projekcie mam tylko jedno działanie.
SeruK
@manjusg Przez cały czas uważałem, że ma to coś wspólnego z niestabilnością AsyncTask, być może bardziej, gdy kilka jest uruchamianych jednocześnie. Jeśli tak, dlaczego?
SeruK
Naprawdę nie wiem, jakie zasady obowiązują w SO dotyczących szybkiego publikowania trzy razy z rzędu, ale znalazłem to w innym wątku ... foo.jasonhudgins.com/2010/05/limitations-of-asynctask.html "AsyncTask używa statyczna wewnętrzna kolejka robocza z zakodowanym limitem 10 elementów ”. To może coś udowodnić, ale mam tylko dwa wystąpienia podklas AsyncTask! Naprawdę chciałbym uniknąć używania normalnych metod wątkowania, ponieważ w końcu będzie dużo przetwarzania wykonanego w dere.
SeruK
2

Android jest brutalny! Nie mogę w to uwierzyć, co za krucha implementacja, która zmienia się z dnia na dzień. Jednego dnia to pojedynczy wątek, następnego jego 5, drugi to 128.

W każdym razie tutaj jest prawie spadek wymiany zapasowego AsyncTask. Możesz nawet nazwać go AsyncTask, jeśli chcesz, ale aby uniknąć nieporozumień, nazywa się go ThreadedAsyncTask. Musisz wywołać executeStart () zamiast execute, ponieważ execute () jest ostateczna.

/**
 * @author Kevin Kowalewski
 *
 */
public abstract class ThreadedAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> { 
    public AsyncTask<Params, Progress, Result> executeStart(Params... params){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
            return executePostHoneycomb(params);
        }else{
            return super.execute(params);
        }
    }


    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private AsyncTask<Params, Progress, Result> executePostHoneycomb(Params... params){
        return super.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); 
    }
}
Kevin Parker
źródło
Czekaj, nie mówisz, że niektóre wersje Androida ograniczają operacje asynchroniczne do jednego wątku, prawda? To byłoby niesamowicie głupie. (Przez jakiś czas nie grałem na Androida. :))
SeruK
Tak, w systemie Android 3.0+, jeśli nie używasz AsyncTask.THREAD_POOL_EXECUTOR, masz tylko jedną pulę wątków. Wypróbuj sam, dwa AsyncTask i po prostu śpij w jednym doInBackground. Z dokumentacji systemu Android AsyncTask: „Począwszy od HONEYCOMB, zadania są wykonywane w jednym wątku, aby uniknąć typowych błędów aplikacji spowodowanych wykonywaniem równoległym”.
Kevin Parker
1

Wiem, że może to być naprawdę spóźniony wątek, ale jest powód, dla którego nie będzie działać na późniejszych emulatorach Androida. Kiedy asynctask został wprowadzony, android pozwolił ci uruchamiać tylko jedno na raz, a później, nie jestem pewien, która wersja, pozwoliła ci uruchomić wiele asynctask naraz, spowodowało to problemy w wielu aplikacjach, więc w Honeycomb + powrócili do tylko pozwalając na wykonanie jednego zadania asynchronicznego naraz. Chyba że ręcznie zmienisz pulę wątków. Mam nadzieję, że to wyjaśnia jedną lub dwie rzeczy dla ludzi.

Fre AkyTurtle-Records
źródło
0

myślę, że to sdk. miałem ten sam problem i po zmianie docelowego sdk z 15 na 11 wszystko działa idealnie.

z sdk15, mimo że AsyncTask.Status jest RUNNING, metoda doInBackground nigdy nie jest wywoływana. myślę, że ma to coś wspólnego z wątkiem interfejsu użytkownika.

phobus
źródło
Nie mogę zaprzeczyć ani potwierdzić, ponieważ nie mam teraz czasu, aby to przetestować. Mogę tylko powiedzieć, że korzystałem z SDK 15, więc jest to bardzo prawdopodobne.
SeruK
0

Na podstawie odpowiedzi Matthieu, poniżej klasy pomocniczej do AsyncTaskprawidłowego wykonania w zależności od wersji SDK, aby uniknąć powielania kodu w aplikacji:

import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Build;

public class AsyncTaskExecutor<Params, Progress, Result> {

  @SuppressLint("NewApi")
  public AsyncTask<Params, Progress, Result> execute(final AsyncTask<Params, Progress, Result> asyncTask, final Params... params){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
      return asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }  else{
      return asyncTask.execute(params);
    }
  }

}

Przykład użycia:

public class MyTask extends AsyncTask<Void, Void, List<String>> {

...

final MyTask myTask = new MyTask();
new AsyncTaskExecutor<Void, Void, List<String>>().execute(myTask);
LG
źródło