Android SDK AsyncTask doInBackground não em execução (subclasse)

90

Em 15/02/2012, ainda não encontrei uma boa explicação para nem uma razão para que isso não funcione. O mais próximo de uma solução é usar a abordagem tradicional de Thread , mas então por que incluir uma classe que (parece) não funciona no Android SDK?

Tarde ASSIM!

Eu tenho uma subclasse 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

Isso é executado assim:

xmlAsync xmlThread = new xmlAsync();

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

Agora, essa subclasse deu um pequeno erro. Anteriormente, ele fazia uma análise de xml, mas quando percebi que doInBackground () não era chamado , eliminei , linha por linha, finalmente terminando apenas com isto:

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

Que, por algum motivo, não registrou nada. No entanto, adicionei isto:

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

E essa linha é de fato registrada ao executar o thread. Então, de alguma forma, onPreExecute () é chamado, mas não doInBackground () . Tenho outra AsyncTask em execução em segundo plano ao mesmo tempo que funciona muito bem.

Atualmente, estou executando o aplicativo em um emulador, SDK versão 15, Eclipse, Mac OS X 10.7.2, próximo ao Pólo Norte.

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 () adiciona mais ou menos uma linha a um SQLiteDatabase, inicializado com o contexto da atividade. publishProgress () é chamado pelo retorno de chamada do Interface ParseListener. No entanto, como eu nem mesmo faço nada, exceto log.v em doInBackground (), primeiro achei isso desnecessário até mesmo abrir.

EDIT 2:

Tudo bem, só para ficar bem claro, essa é a outra AsyncTask, executando na mesma atividade e funcionando perfeitamente.

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

EDITAR 3:

Sigh, desculpe, eu sou ruim em fazer perguntas. Mas aqui está a inicialização do 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);
}
SeruK
fonte
é possível que a atividade esteja terminando antes da conclusão da tarefa?
Paul Nikonowicz
Altamente improvável. Nesse caso, meu outro tópico não estaria funcionando direito? E eu só tenho uma atividade agora.
SeruK
2
Meu aplicativo tem o mesmo problema - doInBackground não é chamado ou é chamado com um atraso muito longo. Aqui está minha observação limitada: exatamente o mesmo código funciona perfeitamente em um smartphone Android 2.3.3 e um emulador Android 2.3.3, mas tem esse problema em um tablet Android 4.0.3 e um monte de emuladores Android 4.xx. É muito tentador concluir que esse problema foi introduzido em versões mais recentes do Android.
Hong
Desculpe, mas esqueci de mencionar que esse problema ocorre apenas com a segunda AsyncTask de uma atividade. A primeira AsyncTask sempre funciona bem.
Hong
Hong, você já tentou a resposta de Matthieu? Estou quase totalmente fora do caixa eletrônico do jogo Android e não trabalho com ele há um tempo, então não posso dizer se suas respostas realmente funcionam. Se não for por você, então talvez tenha sido ruim de minha parte aceitar a resposta dele ...
SeruK

Respostas:

107

A solução de Matthieu funcionará bem para a maioria, mas alguns podem enfrentar problemas; a menos que procure em muitos links fornecidos aqui ou na web, como Anders Göransson a explicação de . Estou tentando resumir algumas outras leituras aqui e explicar rapidamente a solução se executeOnExecutor ainda estiver funcionando em um único thread ...

O comportamento do AsyncTask().execute();mudou nas versões do Android. Antes do Donut (Android: 1.6 API: 4), as tarefas eram executadas em série, do Donut ao Gingerbread (Android: 2.3 API: 9), as tarefas paralelo; uma vez que a execução do Honeycomb (Android: 3.0 API: 11) foi alternada de volta para sequencial; um novo método AsyncTask().executeOnExecutor(Executor), entretanto, foi adicionado para execução paralela.

No processamento sequencial, todas as tarefas Async são executadas em um único thread e, portanto, têm que esperar antes que a tarefa anterior termine. Se você precisa executar o código imediatamente, precisa que as tarefas sejam processadas em paralelo em threads separados.

Com AsyncTask, a execução serial não está disponível entre as versões Donut e Honeycomb, enquanto a execução paralela não está disponível antes de Donut.

Para processamento paralelo após Donut: Verifique a versão Build e com base nesse método use .execute () ou .executeOnExecutor (). O código a seguir pode ajudar ...

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:A função .executeOnExecutor()verifica se o targetSdkVersionprojeto é menor ou igual a HONEYCOMB_MR1(Android: 2.1 API: 7) e então força o executor a ser THREAD_POOL_EXECUTOR(que executa tarefas sequencialmente no pós-Honeycomb).
Se você não definiu um, targetSdkVersionentão minSdkVersioné automaticamente considerado o targetSdkVersion.
Portanto, para executar sua AsyncTask em paralelo no post Honeycomb, você não pode deixar targetSdkVersionvazio.

Nashe
fonte
1
Muito boa resposta. Embora o de Matthieu não esteja errado, estou aceitando isso, já que você adiciona um monte de informações importantes.
SeruK
@Nashe Muito obrigado. É realmente muito útil. Eu estava lutando com esse mesmo problema por 3 dias. Obrigado mais uma vez :)
Salvei meu dia! Desejo que esta resposta seja mais fácil de encontrar.
zjk de
4
Ei @Nashe, Meu problema é um pouco estranho. Antes de hoje eu estava usando o método .execute () em AsyncTask e o código estava funcionando perfeitamente. Mas hoje eu peguei o problema - o controle não vai para o método doInBackground (). Embora a solução que você forneceu esteja funcionando, estou intrigado como funcionava antes sem a solução. Estou usando o mesmo conjunto de dispositivos antes e agora.
Ravi Sisodia
Por que deveria ser assim? Por que não funciona de acordo com as expectativas? :(
Nikolay R
160

Você deve verificar esta resposta: https://stackoverflow.com/a/10406894/347565 e o link para grupos do google que ele inclui.

Eu tive um problema parecido com o seu, ainda não sei por que não está funcionando, mas mudei meu código assim e o problema desapareceu:

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
fonte
Tenho sido muito ruim em olhar para este post; Há muito tempo me afastei do projeto. Vou aceitar isso como a resposta, já que as pessoas parecem dizer que funciona.
SeruK de
Isso funcionou para mim agora, mas gostaria de entender o porquê. estava funcionando bem com executar antes de parar. uma coisa que estou fazendo é iniciar uma nova asynctask dentro de onPostExecute da mesma asynctask (ou seja, estou chamando recursivamente). Talvez isso esteja relacionado ao problema?
steveh de
Acho que isso deve lhe dar todas as explicações de que você precisa: commonsware.com/blog/2012/04/20/…
Matthieu
1
@Matthieu: Senhor, realmente não posso agradecer o suficiente !!! Eu estava batendo minha cabeça nesse problema por horas e sua solução fez tudo funcionar como um encanto! Muito obrigado pela resposta fantástica. Eu gostaria de poder dar mais de um voto positivo !!
Swayam de
9

Você pode fazer isso de duas maneiras:

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

Caso a maneira 1 não funcione para você, tente a maneira 2 .

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

Espero que isso ajude você.

Hiren Patel
fonte
super cara está funcionando, mas as próximas tarefas Async subsequentes que estão usando AsyncTask.THREAD_POOL_EXECUTOR estão ficando com falha, então eu preciso mudar com CustomExecutor todo o projeto, eu acho :(
kumar
@vinu, sugiro que você use uma tarefa assíncrona comum e um método comum para executar AsyncTask. Espero que isso ajude você.
Hiren Patel
2
Ei, isso me ajudou muito! Obrigado!
Justin Ebby
6

Eu tive o mesmo problema: não consigo executar uma segunda AsyncTask depois de chamar "execute" em uma primeira: doInBackground só é chamado para a primeira.

Para responder por que isso acontece, verifique esta resposta (comportamento diferente dependendo do SDK)

No entanto, para o seu caso, esse obstáculo pode ser evitado usando executeOnExecutor (disponível a partir de 3.0 funcionou para mim usando 4.0.3), mas tome cuidado com as limitações de tamanho e enfileiramento do pool de threads.

Você pode tentar algo assim:

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

Para a sua pergunta de atualização: é explicado na documentação Basicamente apenas para evitar todos os problemas que podem surgir de multithreading como intereferência ....

code7amza
fonte
5

Uma coisa que eu gostaria de saber, e isso pode realmente corrigir seu problema, é onde você está instanciando a instância de sua classe e chamando o método execute ()? Se você ler a documentação para AsyncTask, ambas as operações precisam ocorrer no thread de IU principal. Se você estiver criando seu objeto e chamando execute de algum outro thread, então onPreExecute pode disparar, não estou 100% certo aqui, mas o thread de segundo plano não será criado e executado.

Se você estiver criando a instância de sua AsyncTask a partir de um thread de segundo plano ou alguma outra operação que não esteja ocorrendo no thread de IU principal, poderá considerar o uso do método: Activity.runOnUiThread (Runnable)

Você precisaria de acesso a uma instância de sua Activity em execução para chamar esse método, mas isso permitirá que você execute código no UI thread a partir de algum outro código que não está sendo executado no UI thread.

Espero que isso faça sentido. Deixe-me saber se eu puder ajudar mais.

David

David C. Sainte-Claire
fonte
adicionando à sua resposta este tópico tem uma resposta interessante stackoverflow.com/questions/4080808/… .
manjusg
Obrigado pela ótima resposta! Aumentei isso porque acho que pode ser um problema comum para iniciantes em AsyncTask. Infelizmente, não é bem a resposta certa para esse problema. Ambas as classes são instanciadas em uma atividade onCreate () em execução no thread de interface do usuário principal. Eu só tenho uma atividade neste projeto.
SeruK
@manjusg Eu considerei o tempo todo que tinha algo a ver com o AsyncTask ser instável, talvez ainda mais quando vários são executados simultaneamente. Se sim, por quê?
SeruK
Eu realmente não sei quais políticas o SO tem sobre postar rapidamente três vezes seguidas, mas encontrei isso no outro tópico ... foo.jasonhudgins.com/2010/05/limitations-of-asynctask.html "AsyncTask usa uma fila de trabalho interna estática com um limite codificado de 10 elementos. " Isso pode provar algo, mas eu só tenho duas instâncias de subclasses de AsyncTask! Eu realmente gostaria de evitar o uso dos métodos normais de threading, já que eventualmente haverá muita análise feita aqui.
SeruK
2

Android é brutal! Eu não posso acreditar nisso, que implementação instável que muda de um dia para o outro. Um dia é um único thread, no outro é 5 e o outro é 128.

De qualquer forma, aqui está quase uma queda na substituição do estoque AsyncTask. Você pode até chamá-lo de AsyncTask se quiser, mas para evitar confusão é chamado de ThreadedAsyncTask. Você precisa chamar executeStart () em vez de execute porque execute () é 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
fonte
Espere, você não está dizendo que algumas versões do Android restringem as operações assíncronas a um tópico, está? Isso seria incrivelmente bobo. (Eu estive fora do jogo Android por um tempo. :))
SeruK
Sim, no Android 3.0+, se você não usar AsyncTask.THREAD_POOL_EXECUTOR, você obterá apenas um pool de threads de um. Experimente você mesmo, duas AsyncTask e durma no doInBackground. Da documentação do Android AsyncTask: "Começando com HONEYCOMB, as tarefas são executadas em um único encadeamento para evitar erros de aplicativo comuns causados ​​pela execução paralela."
Kevin Parker,
1

Sei que pode ser muito tarde para o tópico, mas há um motivo pelo qual ele não funcionará em emuladores Android posteriores. Quando asynctask foi introduzido, o Android só permitia que você executasse um por vez, então algum tempo depois, não tenho certeza de qual versão, eles permitiam que você executasse várias tarefas async ao mesmo tempo, isso causou problemas em muitos aplicativos e, portanto, no Honeycomb + eles voltaram para apenas permitindo que uma tarefa assíncrona seja executada por vez. A menos que você altere manualmente o pool de threads. Espero que isso esclareça uma ou duas coisas para as pessoas.

Fre AkyTurtle-Records
fonte
0

Eu acho que é o SDK. Eu tive o mesmo problema, e depois de mudar o alvo sdk de 15 para 11, tudo funciona perfeitamente.

com sdk15, mesmo que AsyncTask.Status esteja RUNNING, doInBackground nunca é chamado. Eu acho que tem algo a ver com o tópico da interface do usuário.

fobo
fonte
Não posso negar ou confirmar, pois não tenho tempo agora para testá-lo. Tudo o que posso dizer é que estava usando o SDK 15, então é muito provável.
SeruK
0

Com base na resposta de Matthieu, segue abaixo uma classe auxiliar para executar seu AsyncTaskcorretamente dependendo da versão do SDK para evitar a duplicação de código em seu aplicativo:

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

}

Exemplo de uso:

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

...

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