Android SDK AsyncTask doInBackground no se está ejecutando (subclase)

90

Hasta el 15/2/2012 todavía no he encontrado una buena explicación ni una razón por la que esto no funciona. Lo más cercano a una solución es usar el enfoque tradicional de Thread , pero entonces, ¿por qué incluir una clase que no (parece) funcionar en el SDK de Android?

¡Incluso así!

Tengo una subclase 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

Eso se ejecuta así:

xmlAsync xmlThread = new xmlAsync();

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

Ahora bien, esta subclase se ha encontrado con un pequeño error. Anteriormente, hizo un análisis de xml, pero cuando noté que no se llamaba doInBackground () , lo eliminé , línea por línea, y finalmente terminé con esto:

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

Que, por alguna razón, no registró nada. Sin embargo, agregué esto:

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

Y esa línea de hecho se registra al ejecutar el hilo. Entonces, de alguna manera, se llama a onPreExecute () pero no a doInBackground () . Tengo otra AsyncTask ejecutándose en segundo plano al mismo tiempo que funciona bien.

Actualmente estoy ejecutando la aplicación en un emulador, SDK versión 15, Eclipse, Mac OS X 10.7.2, cerca del Polo 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 () más o menos agrega una fila a una SQLiteDatabase, inicializada con el contexto de la actividad. publishProgress () es llamado por la devolución de llamada de Interface ParseListener. Sin embargo, dado que ni siquiera hago nada excepto log.v en doInBackground (), primero encontré esto innecesario incluso para abrirlo.

EDITAR 2:

Muy bien, para que quede perfectamente claro, esta es la otra AsyncTask, ejecutándose en la misma actividad y funcionando perfectamente 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();
    }
}

EDITAR 3:

Suspiro, lo siento, soy malo haciendo preguntas. Pero aquí está la inicialización de las tareas.

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
fuente
¿Es posible que la actividad termine antes de que se complete la tarea?
Paul Nikonowicz
Altamente improbable. En ese caso, mi otro hilo no funcionaría correctamente. Y ahora solo tengo una actividad.
SeruK
2
Mi aplicación tiene el mismo problema: doInBackground no se llama o no se llama con un retraso muy largo. Aquí está mi observación limitada: exactamente el mismo código funciona perfectamente en un teléfono inteligente Android 2.3.3 y un emulador de Android 2.3.3, pero tiene este problema en una tableta Android 4.0.3 y un montón de emuladores de Android 4.xx. Es muy tentador concluir que este problema se introdujo en las versiones más recientes de Android.
Hong
Lo siento, pero olvidé mencionar que este problema ocurre solo con la segunda AsyncTask de una actividad. La primera AsyncTask siempre funciona bien.
Hong
Hong, ¿has probado la respuesta de Matthieu? Casi no estoy en el cajero automático del juego de Android y no he trabajado con él durante un tiempo, por lo que no puedo decir si sus respuestas realmente funcionan. Si no es para ti, entonces quizás fue malo de mi parte aceptar su respuesta ...
SeruK

Respuestas:

107

La solución de Matthieu funcionará bien para la mayoría, pero algunos pueden enfrentar problemas; a menos que busque en muchos enlaces proporcionados aquí o desde la web, como la explicación de Anders Göransson . Estoy tratando de resumir algunas otras lecturas aquí y explicar rápidamente la solución si executeOnExecutor todavía está funcionando en un solo hilo ...

El comportamiento de AsyncTask().execute();ha cambiado a través de las versiones de Android. Antes de Donut (Android: 1.6 API: 4) las tareas se ejecutaban en serie, desde Donut hasta Gingerbread (Android: 2.3 API: 9) las tareas se ejecutaban en paralelo; desde que la ejecución de Honeycomb (Android: 3.0 API: 11) se cambió de nuevo a secuencial; AsyncTask().executeOnExecutor(Executor)sin embargo, se agregó un nuevo método para la ejecución en paralelo.

En el procesamiento secuencial, todas las tareas Async se ejecutan en un solo hilo y, por lo tanto, deben esperar antes de que finalice la tarea anterior. Si necesita ejecutar código inmediatamente, necesita que las tareas se procesen en paralelo en subprocesos separados.

Con AsyncTask, la ejecución en serie no está disponible entre las versiones de Donut y Honeycomb, mientras que la ejecución en paralelo no está disponible antes de Donut.

Para el procesamiento paralelo después de Donut: verifique la versión de compilación y, en base a eso, use el método .execute () o .executeOnExecutor (). El siguiente código puede ayudar ...

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 función .executeOnExecutor()comprueba si el targetSdkVersionproyecto es menor o igual a HONEYCOMB_MR1(Android: 2.1 API: 7) y luego obliga al ejecutor a serlo THREAD_POOL_EXECUTOR(que ejecuta Tareas secuencialmente en la publicación Honeycomb).
Si no ha definido un targetSdkVersion, minSdkVersionse considera automáticamente que es el targetSdkVersion.
Por lo tanto, para ejecutar su AsyncTask en paralelo en la publicación Honeycomb, no puede dejarlo targetSdkVersionvacío.

Nashe
fuente
1
Muy buena respuesta. Si bien Matthieu no está mal, acepto esto, ya que agregas un montón de información importante.
SeruK
@Nashe Muchas gracias. Es realmente muy útil. Estuve luchando con este mismo problema durante 3 días. Gracias de nuevo :)
¡Salvó mi día! Ojalá esta respuesta sea más fácil de encontrar.
zjk
4
Hola @Nashe, mi problema es un poco incómodo. Antes de hoy, estaba usando el método .execute () en AsyncTask y el código funcionaba perfectamente. Pero hoy tengo el problema: el control no entra en el método doInBackground (). Aunque la solución que proporcionas funciona, me sorprende cómo funcionaba antes sin la solución. Estoy usando el mismo conjunto de dispositivos antes y ahora.
Ravi Sisodia
¿Por qué debería ser así? ¿Por qué no funciona según las expectativas? :(
Nikolay R
160

Debe consultar esta respuesta: https://stackoverflow.com/a/10406894/347565 y el enlace a los grupos de Google que incluye.

Tuve un problema similar al tuyo, todavía no está claro por qué no funciona, pero cambié mi código de esta manera y el problema desapareció:

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
fuente
He sido muy malo mirando hacia atrás en esta publicación; Hace mucho que me alejé del proyecto. Aceptaré esto como la respuesta, ya que la gente parece decir que funciona.
SeruK
esto funcionó para mí en este momento, pero desearía entender por qué. estaba funcionando bien con ejecutar antes de que se detuviera. una cosa que estoy haciendo es iniciar un nuevo asynctask dentro del onPostExecute del mismo asynctask (es decir, lo llamo de forma recursiva) ¿tal vez esté conectado con el problema?
steveh
Creo que esto debería darle todas las explicaciones que necesita: commonsware.com/blog/2012/04/20/…
Matthieu
1
@Matthieu: Señor, ¡no puedo agradecerle lo suficiente! ¡Me había estado golpeando la cabeza con este problema durante horas y su solución hizo que todo funcionara a la perfección! Muchas gracias por la fantástica respuesta. ¡Ojalá pudiera dar más de un voto a favor!
Swayam
9

Puede hacer esto de dos maneras:

Camino 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 caso de que la forma 1 no funcione, pruebe la forma 2 .

Camino 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 esto te ayudará.

Hiren Patel
fuente
super dude, está funcionando, pero las siguientes tareas Async posteriores que usan AsyncTask.THREAD_POOL_EXECUTOR están fallando, así que necesito cambiar con CustomExecutor en todo el proyecto, supongo :(
kumar
@vinu, te sugiero que uses una tarea asíncrona común y un método común para ejecutar AsyncTask. Espero que esto te ayudará.
Hiren Patel
2
¡Oye, esto me ayudó mucho! ¡Gracias!
Justin Ebby
6

Tuve el mismo problema: no se puede ejecutar una segunda AsyncTask después de que llamé a "ejecutar" en la primera: solo se llama a doInBackground para la primera.

Para responder por qué sucede esto, marque esta respuesta (comportamiento diferente según el SDK)

Sin embargo, para su caso, este obstáculo se puede evitar usando executeOnExecutor (disponible a partir de 3.0 funcionó para mí usando 4.0.3) pero tenga cuidado con las limitaciones del tamaño del grupo de subprocesos y la cola.

¿Puedes probar algo como esto?

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 su pregunta de actualización: se explica en los documentos Básicamente solo para evitar todos los problemas que pueden provenir de subprocesos múltiples como interferencia ...

code7amza
fuente
5

Una cosa que me gustaría saber, y que en realidad podría solucionar su problema, es ¿dónde está creando una instancia de su clase y llamando al método execute ()? Si lee la documentación de AsyncTask, ambas operaciones deben tener lugar en el hilo principal de la IU. Si está creando su objeto y llamando a ejecutar desde algún otro hilo, entonces onPreExecute podría dispararse, no estoy 100% seguro aquí, pero el hilo de fondo no se creará ni ejecutará.

Si está creando la instancia de su AsyncTask desde un subproceso en segundo plano, o alguna otra operación que no se lleva a cabo en el subproceso principal de la interfaz de usuario, podría considerar usar el método: Activity.runOnUiThread (Runnable)

Necesitaría acceder a una instancia de su Actividad en ejecución para llamar a ese método, pero le permitirá ejecutar código en el subproceso de la IU desde algún otro código que no se esté ejecutando en el subproceso de la IU.

Espero que tenga sentido. Hágame saber si puedo ayudar más.

David

David C. Sainte-Claire
fuente
agregando a su respuesta, este hilo tiene una respuesta interesante stackoverflow.com/questions/4080808/… .
manjusg
¡Gracias por la gran respuesta! Subí esto porque creo que podría ser un problema común para principiantes de AsyncTask. Por desgracia, no es la respuesta correcta para este problema. Ambas clases se instancian en onCreate () de una actividad que se ejecuta en el hilo principal de la interfaz de usuario. Solo tengo una actividad en este proyecto.
SeruK
@manjusg He considerado todo el tiempo que tiene algo que ver con que AsyncTask sea inestable, tal vez más cuando se ejecutan varios simultáneamente. Si es así, ¿por qué?
SeruK
Realmente no sé qué políticas tiene SO sobre la publicación rápida tres veces seguidas, pero encontré esto en el otro hilo ... foo.jasonhudgins.com/2010/05/limitations-of-asynctask.html "Usos de AsyncTask una cola de trabajo interna estática con un límite codificado de 10 elementos ". Esto podría probar algo, ¡pero solo tengo dos instancias de subclases de AsyncTask! Realmente me gustaría evitar el uso de los métodos de subprocesamiento normales, ya que eventualmente se realizará una gran cantidad de análisis en dere.
SeruK
2

¡Android es brutal! No puedo creer esto, qué implementación tan simple que cambia de un día para otro. Un día es un solo hilo, al siguiente es 5 y el otro es 128.

De todos modos, aquí hay un reemplazo cercano a la caída de la acción AsyncTask. Incluso puede llamarlo AsyncTask si lo desea, pero para evitar confusiones se llama ThreadedAsyncTask. Debe llamar a executeStart () en lugar de ejecutar porque execute () es 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
fuente
Espera, no estás diciendo que algunas versiones de Android restringen las operaciones asíncronas a un hilo, ¿verdad? Eso sería increíblemente tonto. (He estado fuera del juego de Android por un tiempo. :))
SeruK
Sí, en Android 3.0+ si no usas AsyncTask.THREAD_POOL_EXECUTOR solo obtienes un grupo de subprocesos de uno. Pruébelo usted mismo, dos AsyncTask y simplemente duerma en el doInBackground de uno. De la documentación de Android AsyncTask: "A partir de HONEYCOMB, las tareas se ejecutan en un solo hilo para evitar errores de aplicación comunes causados ​​por la ejecución paralela".
Kevin Parker
1

Sé que esto puede ser muy tarde para el hilo, pero hay una razón por la que no funcionará en los emuladores de Android posteriores. Cuando se introdujo asynctask, Android solo le permitió ejecutar una a la vez, luego, en algún momento, no estoy seguro de qué versión, le permitieron ejecutar múltiples asynctasks a la vez, esto causó problemas en muchas aplicaciones, por lo que en Honeycomb + volvieron a solo permitiendo que se ejecute un asynctask a la vez. A menos que cambie manualmente el grupo de subprocesos. Espero que aclare una o dos cosas para la gente.

Fre AkyTurtle-Records
fuente
0

Creo que es el SDK. Tuve el mismo problema, y ​​después de cambiar el SDK de destino de 15 a 11, todo funciona perfectamente.

con sdk15, aunque AsyncTask.Status está EN EJECUCIÓN, nunca se llama a doInBackground. Aunque creo que tiene algo que ver con el hilo de la interfaz de usuario.

fobo
fuente
No puedo negarlo ni confirmarlo, ya que realmente no tengo tiempo ahora para probarlo. Todo lo que puedo decir es que estaba usando SDK 15, por lo que es muy probable.
SeruK
0

Según la respuesta de Matthieu, debajo de una clase de ayuda para ejecutar su AsyncTaskcorrectamente dependiendo de la versión del SDK para evitar duplicar el código en su aplicación:

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

}

Ejemplo 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
fuente