Android SDK AsyncTask doInBackground ne fonctionne pas (sous-classe)

90

Au 15/2/2012, je n'ai pas encore trouvé une bonne explication ni une raison pour laquelle cela ne fonctionne pas. Le plus proche d'une solution est d'utiliser l' approche Thread traditionnelle , mais alors pourquoi inclure une classe qui ne fonctionne pas (semble) dans le SDK Android?

Evenin 'SO!

J'ai une sous-classe 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

Cela est exécuté comme ceci:

xmlAsync xmlThread = new xmlAsync();

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

Maintenant, cette sous-classe a rencontré une petite erreur. Auparavant, il effectuait une analyse XML, mais quand j'ai remarqué que doInBackground () n'était pas appelé, je l'ai dépouillé, ligne par ligne, pour finalement aboutir à ceci:

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

Qui, pour une raison quelconque, n'a rien enregistré. Cependant, j'ai ajouté ceci:

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

Et cette ligne est en effet journalisée lors de l'exécution du thread. Donc en quelque sorte onPreExecute () est appelé mais pas doInBackground () . J'ai une autre AsyncTask en arrière-plan en même temps qui fonctionne très bien.

J'exécute actuellement l'application sur un émulateur, SDK version 15, Eclipse, Mac OS X 10.7.2, près du pôle Nord.

ÉDITER:

@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 () ajoute plus ou moins une ligne à une SQLiteDatabase, initialisée avec le contexte de l'activité. publishProgress () est appelé par le rappel de l'interface ParseListener. Cependant, comme je ne fais même rien d'autre que log.v dans doInBackground (), j'ai d'abord trouvé cela inutile même d'évoquer.

MODIFIER 2:

D'accord, juste pour être parfaitement clair, c'est l'autre AsyncTask, exécutée dans la même activité et fonctionnant parfaitement bien.

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

MODIFIER 3:

Soupir, désolé, je suis mauvais pour poser des questions. Mais voici l'initialisation des tâches.

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
la source
est-il possible que l'activité se termine avant la fin de la tâche?
Paul Nikonowicz
Hautement improbable. Dans ce cas, mon autre thread ne fonctionnerait pas correctement? Et je n'ai qu'une seule activité pour le moment.
SeruK
2
Mon application a le même problème - doInBackground n'est pas appelé ou appelé avec un très long délai. Voici mon observation limitée: exactement le même code fonctionne parfaitement sur un smartphone Android 2.3.3 et un émulateur Android 2.3.3, mais a ce problème sur une tablette Android 4.0.3 et un tas d'émulateurs Android 4.xx. Il est très tentant de conclure que ce problème a été introduit dans les nouvelles versions d'Android.
Hong
Désolé, mais j'ai oublié de mentionner que ce problème se produit uniquement avec la deuxième AsyncTask d'une activité. La première AsyncTask fonctionne toujours correctement.
Hong
Hong, as-tu essayé la réponse de Matthieu? Je suis principalement hors du guichet automatique du jeu Android et je n'ai pas travaillé avec lui depuis un certain temps, donc je ne peux pas dire si ses réponses fonctionnent réellement. Si ce n'est pas pour vous, alors peut-être que c'était mal de ma part d'accepter sa réponse ...
SeruK

Réponses:

107

La solution de Matthieu fonctionnera bien pour la plupart, mais certains peuvent rencontrer des problèmes; à moins de creuser dans de nombreux liens fournis ici ou sur le Web, comme l' explication d' Anders Göransson . J'essaie de résumer quelques autres lectures ici et d'expliquer rapidement la solution si executeOnExecutor fonctionne toujours en un seul thread ...

Le comportement de AsyncTask().execute();a changé avec les versions d'Android. Avant Donut (Android: 1.6 API: 4), les tâches étaient exécutées en série, de Donut à Gingerbread (Android: 2.3 API: 9), les tâches exécutées en parallèle; depuis l' exécution de Honeycomb (Android: 3.0 API: 11) est revenue au séquentiel; une nouvelle méthode a AsyncTask().executeOnExecutor(Executor)cependant été ajoutée pour l'exécution parallèle.

Dans le traitement séquentiel, toutes les tâches Async s'exécutent dans un seul thread et doivent donc attendre la fin de la tâche précédente. Si vous devez exécuter du code immédiatement, vous avez besoin que les tâches soient traitées en parallèle dans des threads séparés.

Avec AsyncTask, l'exécution en série n'est pas disponible entre les versions Donut et Honeycomb, tandis que l'exécution parallèle n'est pas disponible avant Donut.

Pour le traitement parallèle après Donut: vérifiez la version de Build et en fonction de celle-ci, utilisez la méthode .execute () ou .executeOnExecutor (). Le code suivant peut vous aider ...

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:La fonction .executeOnExecutor()vérifie si le targetSdkVersionprojet est inférieur ou égal à HONEYCOMB_MR1(Android: 2.1 API: 7) puis force l'exécuteur à être THREAD_POOL_EXECUTOR(qui exécute les tâches séquentiellement dans post Honeycomb).
Si vous n'avez pas défini de, targetSdkVersionalors minSdkVersionest automatiquement considéré comme le targetSdkVersion.
Par conséquent, pour exécuter votre AsyncTask en parallèle sur post Honeycomb, vous ne pouvez pas laisser targetSdkVersionvide.

Nashe
la source
1
Très bonne réponse. Bien que Matthieu ne se trompe pas, j'accepte cela, puisque vous ajoutez un tas d'informations importantes.
SeruK
@Nashe Merci beaucoup. C'est vraiment très utile. Je me suis battu avec ce même problème pendant 3 jours. Merci encore :)
J'ai sauvé ma journée! Je souhaite que cette réponse soit plus facile à trouver.
zjk
4
Hey @Nashe, mon problème est un peu gênant. Avant aujourd'hui, j'utilisais la méthode .execute () sur AsyncTask et le code fonctionnait parfaitement. Mais aujourd'hui, j'ai le problème - le contrôle n'entre pas dans la méthode doInBackground (). Bien que la solution que vous fournissez fonctionne, je suis étonné de savoir comment cela fonctionnait auparavant sans la solution. J'utilise le même ensemble d'appareils avant et maintenant.
Ravi Sisodia
Pourquoi cela devrait-il être ainsi? Pourquoi cela ne fonctionne pas selon les attentes? :(
Nikolay R
160

Vous devriez vérifier cette réponse: https://stackoverflow.com/a/10406894/347565 et le lien vers les groupes Google qu'il comprend.

J'ai eu un problème similaire à vous, je ne sais toujours pas pourquoi cela ne fonctionne pas, mais j'ai changé mon code comme ceci et le problème a disparu:

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
la source
J'ai été très mauvais en regardant ce post; Je me suis éloigné depuis longtemps du projet. J'accepterai simplement ceci comme réponse puisque les gens semblent dire que cela fonctionne.
SeruK
cela a fonctionné pour moi tout à l'heure mais j'aurais aimé comprendre pourquoi. il fonctionnait bien avec exécuter avant de s'arrêter. une chose que je fais est de démarrer une nouvelle asynctask dans le onPostExecute du même asynctask (c'est-à-dire que je l'appelle récursivement) peut-être que c'est lié au problème?
steveh
Je pense que cela devrait vous donner toutes les explications dont vous avez besoin: commonsware.com/blog/2012/04/20
Matthieu
1
@Matthieu: Monsieur, je ne peux vraiment pas vous remercier assez !!! Cela faisait des heures que je me battais la tête sur ce problème et votre solution a tout fait fonctionner comme un charme! Merci beaucoup pour la réponse fantastique. Je souhaite que je pourrais donner plus d'un vote positif !!
Swayam
9

Vous pouvez le faire de deux manières:

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

En cas de façon 1 fonctionne pas pour vous essayer ainsi 2 .

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

J'espère que ceci vous aidera.

Hiren Patel
la source
super mec son travail mais les prochaines tâches Async suivantes qui utilisent AsyncTask.THREAD_POOL_EXECUTOR échouent, donc je dois changer avec CustomExecutor partout dans le projet je suppose :(
kumar
@vinu, je vous suggère d'utiliser une tâche asynchrone commune et une méthode commune pour exécuter AsyncTask. J'espère que ceci vous aidera.
Hiren Patel
2
Hé cela m'a beaucoup aidé! Merci!
Justin Ebby
6

J'ai eu le même problème: impossible d'exécuter une deuxième AsyncTask après avoir appelé "exécuter" sur une première: doInBackground n'est appelé que pour le premier.

Pour savoir pourquoi cela se produit, vérifiez cette réponse (comportement différent selon le SDK)

Cependant, pour votre cas, cet obstacle peut être évité en utilisant executeOnExecutor (disponible à partir de 3.0 a fonctionné pour moi en utilisant 4.0.3) mais méfiez-vous des limitations de la taille du pool de threads et de la file d'attente.

Pouvez-vous essayer quelque chose comme ça:

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

Pour votre question de mise à jour: elle est expliquée dans la documentation En gros, juste pour éviter tous les problèmes qui peuvent provenir du multithreading comme l'interférence ....

code7amza
la source
5

Une chose que j'aimerais savoir, et qui pourrait en fait résoudre votre problème, est où instanciez-vous l'instance de votre classe et appelez-vous la méthode execute ()? Si vous lisez la documentation d'AsyncTask, ces deux opérations doivent avoir lieu sur le thread d'interface utilisateur principal. Si vous créez votre objet et appelez execute à partir d'un autre thread, alors onPreExecute peut se déclencher, je ne suis pas sûr à 100% ici, mais le thread d'arrière-plan ne sera pas créé et exécuté.

Si vous créez l'instance de votre AsyncTask à partir d'un thread d'arrière-plan ou d'une autre opération qui n'a pas lieu sur le thread d'interface utilisateur principal, vous pouvez envisager d'utiliser la méthode: Activity.runOnUiThread (Runnable)

Vous auriez besoin d'accéder à une instance de votre activité en cours d'exécution pour appeler cette méthode, mais cela vous permettra d'exécuter du code sur le thread d'interface utilisateur à partir d'un autre code qui ne s'exécute pas sur le thread d'interface utilisateur.

J'espère que cela a du sens. Faites-moi savoir si je peux vous aider davantage.

David

David C. Sainte-Claire
la source
en ajoutant à votre réponse ce fil a une réponse intéressante stackoverflow.com/questions/4080808/… .
manjusg
Merci pour la bonne réponse! J'ai augmenté cela parce que je pense que cela pourrait être un problème courant pour les débutants AsyncTask. Hélas, ce n'est pas tout à fait la bonne réponse à ce problème. Les deux classes sont instanciées dans onCreate () d'une activité s'exécutant sur le thread d'interface utilisateur principal. Je n'ai qu'une seule activité dans ce projet.
SeruK
@manjusg J'ai toujours considéré que cela avait quelque chose à voir avec l'instabilité d'AsyncTask, peut-être davantage quand plusieurs sont exécutés simultanément. Si oui, pourquoi?
SeruK
Je ne sais pas vraiment quelles sont les politiques de SO pour publier rapidement trois fois de suite, mais j'ai trouvé cela dans l'autre fil ... foo.jasonhudgins.com/2010/05/limitations-of-asynctask.html "Utilisations d'AsyncTask une file d'attente de travail interne statique avec une limite codée en dur de 10 éléments. " Cela pourrait prouver quelque chose, mais je n'ai que deux instances de sous-classes AsyncTask! Je voudrais vraiment éviter d'utiliser les méthodes de threading normales car il y aura finalement beaucoup d'analyses effectuées dans dere.
SeruK
2

Android est Brutal! Je ne peux pas croire cela, quelle implémentation floconneuse qui change de jour en jour. Un jour, c'est un seul fil, le suivant, c'est 5, l'autre est 128.

Quoi qu'il en soit, voici une quasi-baisse de remplacement pour le stock AsyncTask. Vous pouvez même l'appeler AsyncTask si vous le souhaitez, mais pour éviter toute confusion, il s'appelle ThreadedAsyncTask. Vous devez appeler executeStart () au lieu de execute car execute () est final.

/**
 * @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
la source
Attendez, vous ne dites pas que certaines versions d'Android limitent les opérations asynchrones à un seul thread, n'est-ce pas? Ce serait incroyablement idiot. (Je suis sorti du jeu Android depuis un moment. :))
SeruK
Oui, sur Android 3.0+, si vous n'utilisez pas AsyncTask.THREAD_POOL_EXECUTOR, vous n'obtenez qu'un pool de threads. Essayez-le pour vous-même, deux AsyncTask et dormez simplement dans son doInBackground. Extrait de la documentation d'Android AsyncTask: «À partir de HONEYCOMB, les tâches sont exécutées sur un seul thread pour éviter les erreurs d'application courantes causées par l'exécution en parallèle.»
Kevin Parker
1

Je sais que cela peut être très tard pour le fil, mais il y a une raison pour laquelle cela ne fonctionnera pas sur les émulateurs Android ultérieurs. Quand asynctask a été introduit, Android ne vous a laissé en exécuter qu'une à la fois, puis un peu plus tard, je ne sais pas quelle version, ils vous ont permis d'exécuter plusieurs asynctasks à la fois, cela a causé des problèmes dans beaucoup d'applications, et donc dans Honeycomb +, ils sont revenus à seulement permettant à une asynctask de s'exécuter à la fois. Sauf si vous modifiez manuellement le pool de threads. J'espère que cela clarifie une ou deux choses pour les gens.

Fre AkyTurtle-Records
la source
0

je pense que c'est le sdk. J'ai eu le même problème, et après avoir changé le SDK cible de 15 à 11, tout fonctionne parfaitement.

avec sdk15, même si le AsyncTask.Status est en cours d'exécution, le doInBackground n'est jamais appelé. Je pense que cela a quelque chose à voir avec le thread de l'interface utilisateur.

phobus
la source
Je ne peux ni nier ni confirmer car je n'ai pas vraiment le temps pour le moment de le tester. Tout ce que je peux dire, c'est que j'utilisais le SDK 15, c'est donc très probable.
SeruK
0

Sur la base de la réponse de Matthieu, ci-dessous une classe d' aide pour exécuter votre AsyncTaskcorrectement en fonction de la version du SDK afin d'éviter de dupliquer du code dans votre application:

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

}

Exemple d'utilisation:

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

...

final MyTask myTask = new MyTask();
new AsyncTaskExecutor<Void, Void, List<String>>().execute(myTask);
LG
la source