90 votes

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

Au 15/2/2012, je n'ai pas encore trouvé de bonne explication ni de raison pour laquelle cela ne fonctionne pas. La solution la plus proche de la réalité est d'utiliser le traditionnel Fil conducteur mais alors pourquoi inclure une classe qui ne fonctionne pas (apparemment) 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

C'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, elle faisait du xml-parsing, mais quand j'ai remarqué que c'était le cas, je me suis dit que c'était le cas. 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;
}

Ce qui, pour une raison quelconque, n'a rien donné. Cependant, j'ai ajouté ceci :

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

Et cette ligne est effectivement enregistrée lors de l'exécution du fil. Ainsi, onPreExecute() est appelé mais pas doInBackground(). . J'ai une autre AsyncTask qui tourne en arrière-plan en même temps et 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.

EDITAR:

@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 callback de l'Interface ParseListener. Cependant, comme je ne fais rien d'autre que log.v dans doInBackground(), j'ai d'abord trouvé inutile de soulever cette question.

EDIT 2 :

Très bien, pour être parfaitement clair, voici l'autre AsyncTask, qui s'exécute dans la même activité et fonctionne 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();
    }
}

EDIT 3 :

Sigh, désolé, je suis mauvais pour poser des questions. Mais voici l'initialisation des Tasks.

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

160voto

Matthieu Points 8603

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

J'ai eu un problème similaire au vôtre, je ne sais toujours pas pourquoi cela ne fonctionne pas, mais j'ai modifié 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);

108voto

Nashe Points 96

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

Comportement de AsyncTask().execute(); a changé au fil des versions d'Android. Avant Donut (Android:1.6 API:4) Les tâches ont été exécutées en série, de Donut a Pain d'épices (Android:2.3 API:9) tâches exécutées en parallèle ; puisque Nid d'abeille (Android:3.0 API:11) l'exécution est redevenue séquentielle ; une nouvelle méthode AsyncTask().executeOnExecutor(Executor) a toutefois été ajouté pour l'exécution parallèle.

Dans le traitement séquentiel, toutes les tâches asynchrones 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 la 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: Fonction .executeOnExecutor() a vérifié si targetSdkVersion du projet est inférieur ou égal à HONEYCOMB_MR1 (Android:2.1 API:7) alors il force l'exécuteur à être THREAD_POOL_EXECUTOR (qui exécute les tâches de manière séquentielle dans le post Honeycomb).
Si vous n'avez pas défini un targetSdkVersion puis minSdkVersion est automatiquement considéré comme le targetSdkVersion .
Par conséquent, pour exécuter votre AsyncTask en parallèle sur le post Honeycomb, vous ne pouvez pas laisser targetSdkVersion vide.

6voto

a_huey Points 66

J'ai eu le même problème : je ne peux pas exécuter une deuxième AsyncTask après avoir appelé "execute" sur une première : doInBackground n'est appelé que pour la première.

Pour savoir pourquoi cela se produit, regardez ceci respuesta (comportement différent selon le SDK)

Cependant, dans votre cas, cet obstacle peut être évité en utilisant executeOnExecutor (disponible à partir de la version 3.0, il a fonctionné pour moi avec la version 4.0.3) mais attention aux limitations de la taille du pool de threads et de la mise en file d'attente.

Pouvez-vous essayer quelque chose comme ceci :

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 sur la mise à jour : elle est expliquée dans le document intitulé docs En fait, il s'agit d'éviter tous les problèmes qui peuvent résulter du multithreading, comme l'interférence .....

5voto

Une chose que j'aimerais savoir, et qui pourrait bien résoudre votre problème, c'est où vous instanciez l'instance de votre classe et appelez la méthode execute() ? Si vous lisez la documentation d'AsyncTask, ces deux opérations doivent avoir lieu sur le thread principal de l'interface utilisateur. Si vous créez votre objet et appelez execute depuis un autre thread, alors onPreExecute peut se déclencher, je n'en suis pas sûr à 100%, 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 toute autre opération ne se déroulant pas sur le thread principal de l'interface utilisateur, vous pouvez envisager d'utiliser cette méthode : Activité.runOnUiThread(Runnable)

Vous devrez avoir accès à 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 de l'interface utilisateur à partir d'un autre code qui n'est pas exécuté sur le thread de l'interface utilisateur.

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

David

2voto

Kevin Points 4235

Android est brutal ! Je n'arrive pas à y croire, quelle implémentation bancale qui change d'un jour à l'autre. Un jour c'est un seul thread, le lendemain c'est 5, l'autre 128.

Quoi qu'il en soit, voici un remplacement presque immédiat de l'AsyncTask de base. Vous pouvez même l'appeler AsyncTask si vous le souhaitez, mais pour éviter toute confusion, il est appelé 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); 
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X