AsyncTask y manejo de errores en Android

147

Estoy convirtiendo mi código de usar Handlera AsyncTask. Lo último es excelente en lo que hace: actualizaciones asíncronas y manejo de resultados en el hilo principal de la interfaz de usuario. Lo que no está claro para mí es cómo manejar las excepciones si algo se vuelve loco AsyncTask#doInBackground.

La forma en que lo hago es tener un controlador de errores y enviarle mensajes. Funciona bien, pero ¿es el enfoque "correcto" o hay una mejor alternativa?

También entiendo que si defino el controlador de errores como un campo de actividad, debería ejecutarse en el hilo de la interfaz de usuario. Sin embargo, a veces (de manera muy impredecible) recibo una excepción que dice que el código activado Handler#handleMessagese ejecuta en el hilo incorrecto. ¿Debería inicializar el controlador de errores en su Activity#onCreatelugar? Colocar runOnUiThreaden Handler#handleMessageparece redundante pero se ejecuta de manera muy confiable.

Bostone
fuente
¿Por qué quieres convertir tu código? ¿Había una buena razón?
HGPB
44
@Haraldo es una mejor práctica de codificación, al menos así es como me siento
Bostone

Respuestas:

178

Funciona bien, pero ¿es el enfoque "correcto" y hay una alternativa mejor?

Me aferro a Throwableo Exceptionen la AsyncTaskinstancia misma y luego hago algo con ella onPostExecute(), por lo que mi manejo de errores tiene la opción de mostrar un diálogo en pantalla.

CommonsWare
fuente
8
¡Brillante! Ya no hay necesidad de
jugar con los controladores
55
¿Es esta la forma en que debo aferrarme al Throwable o Exception? "Agregue una variable de instancia a su propia subclase AsyncTask que contendrá el resultado de su procesamiento en segundo plano". Cuando obtenga una excepción, almacene la excepción (o alguna otra cadena / código de error) en esta variable. Cuando se llama a onPostExecute, vea si esta variable de instancia está configurada con algún error. Si es así, muestre un mensaje de error. "(Del usuario" Streets of Boston " groups.google.com/group/android-developers/browse_thread/thread/… )
OneWorld
1
@OneWorld: Sí, eso debería estar bien.
CommonsWare
2
Hola CW, ¿podría explicar su forma de hacerlo con más detalle, tal vez con un breve ejemplo de código? ¡¡Muchas gracias!!
Bruiser
18
@Bruiser: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/… tiene el AsyncTasksiguiente patrón que describo.
CommonsWare
140

Cree un objeto AsyncResult (que también puede usar en otros proyectos)

public class AsyncTaskResult<T> {
    private T result;
    private Exception error;

    public T getResult() {
        return result;
    }

    public Exception getError() {
        return error;
    }

    public AsyncTaskResult(T result) {
        super();
        this.result = result;
    }

    public AsyncTaskResult(Exception error) {
        super();
        this.error = error;
    }
}

Devuelva este objeto de sus métodos AsyncTask doInBackground y verifíquelo en postExecute. (Puede usar esta clase como una clase base para sus otras tareas asíncronas)

A continuación se muestra una maqueta de una tarea que obtiene una respuesta JSON del servidor web.

AsyncTask<Object,String,AsyncTaskResult<JSONObject>> jsonLoader = new AsyncTask<Object, String, AsyncTaskResult<JSONObject>>() {

        @Override
        protected AsyncTaskResult<JSONObject> doInBackground(
                Object... params) {
            try {
                // get your JSONObject from the server
                return new AsyncTaskResult<JSONObject>(your json object);
            } catch ( Exception anyError) {
                return new AsyncTaskResult<JSONObject>(anyError);
            }
        }

        protected void onPostExecute(AsyncTaskResult<JSONObject> result) {
            if ( result.getError() != null ) {
                // error handling here
            }  else if ( isCancelled()) {
                // cancel handling here
            } else {

                JSONObject realResult = result.getResult();
                // result handling here
            }
        };

    }
Cagatay Kalan
fuente
1
Me gusta. Buena encapsulación. Dado que esta es una paráfrasis de la respuesta original, la respuesta permanece pero esto definitivamente merece un punto
Bostone
Esta es una muy buena demostración de cuán útiles pueden ser los genéricos. Está arrojando un olor extraño en términos de complejidad, pero no de una manera que realmente pueda articular.
num1
44
Buena idea, sólo una pregunta: ¿por qué se llama super()en AsyncTaskResultcuando la clase no se extiende algo?
donturner
77
"sin daños": el código redundante siempre es perjudicial para la legibilidad y el mantenimiento. ¡Sácalo de allí! :)
donturner
2
Realmente me gustó la solución ... ahora que lo pienso: los chicos de C # usaron exactamente el mismo método en la implementación nativa de BackgroundTask correspondiente a C # ...
Vova
11

Cuando siento la necesidad de manejar Excepciones AsyncTaskcorrectamente, lo uso como superclase:

public abstract class ExceptionAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {

    private Exception exception=null;
    private Params[] params;

    @Override
    final protected Result doInBackground(Params... params) {
        try {
            this.params = params; 
            return doInBackground();
        }
        catch (Exception e) {
            exception = e;
            return null;
        }
    }

    abstract protected Result doInBackground() throws Exception;

    @Override
    final protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        onPostExecute(exception, result);
    }

    abstract protected void onPostExecute(Exception exception, Result result);

    public Params[] getParams() {
        return params;
    }

}

Como es normal, anula doInBackgrounden su subclase para hacer el trabajo de fondo, felizmente lanzando excepciones donde sea necesario. Luego se ve obligado a implementar onPostExecute(porque es abstracto) y esto le recuerda suavemente que maneje todos los tipos de Exception, que se pasan como parámetro. En la mayoría de los casos, las excepciones conducen a algún tipo de salida de interfaz de usuario, por lo que onPostExecutees un lugar perfecto para hacerlo.

sulai
fuente
1
Whoa, ¿por qué no simplemente pasar el paramsavance, para que sea más similar al original y más fácil de migrar?
TWiStErRob
@TWiStErRob no tiene nada de malo con esa idea. Supongo que es una cuestión de preferencia personal, ya que tiendo a no usar parámetros. Yo prefiero new Task("Param").execute()más new Task().execute("Param").
sulai
5

Si desea utilizar el marco RoboGuice que le brinda otros beneficios, puede probar RoboAsyncTask, que tiene una devolución de llamada adicional onException (). Funciona muy bien y lo uso. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

Ludwigm
fuente
¿Cuál es tu experiencia con esto? bastante estable?
nickaknudson
¿ RoboGuiceSigue vivo? ¿Parece que no se ha actualizado desde 2012?
Dimitry K
No, RoboGuice está muerto y en desuso. Dagger2 es el reemplazo recomendado, pero es solo una biblioteca DI básica.
Avi Cherry
3

Hice mi propia subclase AsyncTask con una interfaz que define las devoluciones de llamada para el éxito y el fracaso. Entonces, si se lanza una excepción en su AsyncTask, la función onFailure pasa la excepción, de lo contrario, la devolución de llamada onSuccess pasa su resultado. Por qué Android no tiene algo mejor disponible está más allá de mí.

public class SafeAsyncTask<inBackgroundType, progressType, resultType>
extends AsyncTask<inBackgroundType, progressType, resultType>  {
    protected Exception cancelledForEx = null;
    protected SafeAsyncTaskInterface callbackInterface;

    public interface SafeAsyncTaskInterface <cbInBackgroundType, cbResultType> {
        public Object backgroundTask(cbInBackgroundType[] params) throws Exception;
        public void onCancel(cbResultType result);
        public void onFailure(Exception ex);
        public void onSuccess(cbResultType result);
    }

    @Override
    protected void onPreExecute() {
        this.callbackInterface = (SafeAsyncTaskInterface) this;
    }

    @Override
    protected resultType doInBackground(inBackgroundType... params) {
        try {
            return (resultType) this.callbackInterface.backgroundTask(params);
        } catch (Exception ex) {
            this.cancelledForEx = ex;
            this.cancel(false);
            return null;
        }
    }

    @Override
    protected void onCancelled(resultType result) {
        if(this.cancelledForEx != null) {
            this.callbackInterface.onFailure(this.cancelledForEx);
        } else {
            this.callbackInterface.onCancel(result);
        }
    }

    @Override
    protected void onPostExecute(resultType result) {
        this.callbackInterface.onSuccess(result);
    }
}
ErlVolton
fuente
3

Una solución más completa para Cagatay Kalan continuación se muestra :

AsyncTaskResult

public class AsyncTaskResult<T> 
{
    private T result;
    private Exception error;

    public T getResult() 
    {
        return result;
    }

    public Exception getError() 
    {
        return error;
    }

    public AsyncTaskResult(T result) 
    {
        super();
        this.result = result;
    }

    public AsyncTaskResult(Exception error) {
        super();
        this.error = error;
    }
}

ExceptionHandlingAsyncTask

public abstract class ExceptionHandlingAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, AsyncTaskResult<Result>>
{
    private Context context;

    public ExceptionHandlingAsyncTask(Context context)
    {
        this.context = context;
    }

    public Context getContext()
    {
        return context;
    }

    @Override
    protected AsyncTaskResult<Result> doInBackground(Params... params)
    {
        try
        {
            return new AsyncTaskResult<Result>(doInBackground2(params));
        }
        catch (Exception e)
        {
            return new AsyncTaskResult<Result>(e);
        }
    }

    @Override
    protected void onPostExecute(AsyncTaskResult<Result> result)
    {
        if (result.getError() != null)
        {
            onPostException(result.getError());
        }
        else
        {
            onPostExecute2(result.getResult());
        }
        super.onPostExecute(result);
    }

    protected abstract Result doInBackground2(Params... params);

    protected abstract void onPostExecute2(Result result);

    protected void onPostException(Exception exception)
    {
                        new AlertDialog.Builder(context).setTitle(R.string.dialog_title_generic_error).setMessage(exception.getMessage())
                .setIcon(android.R.drawable.ic_dialog_alert).setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener()
                {
                    public void onClick(DialogInterface dialog, int which)
                    {
                        //Nothing to do
                    }
                }).show();
    }
}

Tarea de ejemplo

public class ExampleTask extends ExceptionHandlingAsyncTask<String, Void, Result>
{
    private ProgressDialog  dialog;

    public ExampleTask(Context ctx)
    {
        super(ctx);
        dialog = new ProgressDialog(ctx);
    }

    @Override
    protected void onPreExecute()
    {
        dialog.setMessage(getResources().getString(R.string.dialog_logging_in));
        dialog.show();
    }

    @Override
    protected Result doInBackground2(String... params)
    {
        return new Result();
    }

    @Override
    protected void onPostExecute2(Result result)
    {
        if (dialog.isShowing())
            dialog.dismiss();
        //handle result
    }

    @Override
    protected void onPostException(Exception exception)
    {
        if (dialog.isShowing())
            dialog.dismiss();
        super.onPostException(exception);
    }
}
vahapt
fuente
Obtuve el método getResources () como en myActivity.getApplicationContext (). GetResources ()
Stephane
2

Esta clase simple puede ayudarte

public abstract class ExceptionAsyncTask<Param, Progress, Result, Except extends Throwable> extends AsyncTask<Param, Progress, Result> {
    private Except thrown;

    @SuppressWarnings("unchecked")
    @Override
    /**
     * Do not override this method, override doInBackgroundWithException instead
     */
    protected Result doInBackground(Param... params) {
        Result res = null;
        try {
            res = doInBackgroundWithException(params);
        } catch (Throwable e) {
            thrown = (Except) e;
        }
        return res;
    }

    protected abstract Result doInBackgroundWithException(Param... params) throws Except;

    @Override
    /**
     * Don not override this method, override void onPostExecute(Result result, Except exception) instead
     */
    protected void onPostExecute(Result result) {
        onPostExecute(result, thrown);
        super.onPostExecute(result);
    }

    protected abstract void onPostExecute(Result result, Except exception);
}
Denis
fuente
2

Otra forma que no depende del uso compartido de miembros variables es utilizar cancelar.

Esto es de documentos de Android:

cancelación booleana final pública (boolean mayInterruptIfRunning)

Intenta cancelar la ejecución de esta tarea. Este intento fallará si la tarea ya se completó, ya se canceló o no se pudo cancelar por algún otro motivo. Si tiene éxito, y esta tarea no se ha iniciado cuando se invoca cancelar, esta tarea nunca debería ejecutarse. Si la tarea ya se inició, el parámetro mayInterruptIfRunning determina si el subproceso que ejecuta esta tarea debe interrumpirse en un intento de detener la tarea.

Al llamar a este método, se invocará onCancelled (Object) en el subproceso de la interfaz de usuario después de que doInBackground (Object []) regrese. Llamar a este método garantiza que onPostExecute (Object) nunca se invoque. Después de invocar este método, debe verificar el valor devuelto por isCancelled () periódicamente desde doInBackground (Object []) para finalizar la tarea lo antes posible.

Por lo tanto, puede llamar a cancelar en la instrucción catch y asegurarse de que onPostExcute nunca se llame, sino que onCancelled se invoca en el subproceso de la interfaz de usuario. Para que pueda mostrar el mensaje de error.

Ali
fuente
No puede mostrar el mensaje de error correctamente, ya que no conoce el problema (excepción), aún necesita capturar y devolver un AsyncTaskResult. Además, la cancelación de un usuario no es un error, es una interacción esperada: ¿cómo se distingue entre estos?
TWiStErRob
cancel(boolean)resultando en una llamada a onCancelled()existir desde el principio, pero onCancelled(Result)se agregó en API 11 .
TWiStErRob
0

En realidad, AsyncTask usa FutureTask & Executor, FutureTask admite la cadena de excepciones Primero definamos una clase auxiliar

public static class AsyncFutureTask<T> extends FutureTask<T> {

    public AsyncFutureTask(@NonNull Callable<T> callable) {
        super(callable);
    }

    public AsyncFutureTask<T> execute(@NonNull Executor executor) {
        executor.execute(this);
        return this;
    }

    public AsyncFutureTask<T> execute() {
        return execute(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    @Override
    protected void done() {
        super.done();
        //work done, complete or abort or any exception happen
    }
}

Segundo, usemos

    try {
        Log.d(TAG, new AsyncFutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //throw Exception in worker thread
                throw new Exception("TEST");
            }
        }).execute().get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        //catch the exception throw by worker thread in main thread
        e.printStackTrace();
    }
Sessy
fuente
-2

Personalmente, usaré este enfoque. Puede capturar las excepciones e imprimir el seguimiento de la pila si necesita la información.

hacer que su tarea en segundo plano devuelva un valor booleano.

Es como esto:

    @Override
                protected Boolean doInBackground(String... params) {
                    return readXmlFromWeb(params[0]);
         }

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}
Harry
fuente
-2

Otra posibilidad sería utilizarlo Objectcomo tipo de retorno y onPostExecute()verificar el tipo de objeto. Es corta.

class MyAsyncTask extends AsyncTask<MyInObject, Void, Object> {

    @Override
    protected AsyncTaskResult<JSONObject> doInBackground(MyInObject... myInObjects) {
        try {
            MyOutObject result;
            // ... do something that produces the result
            return result;
        } catch (Exception e) {
            return e;
        }
    }

    protected void onPostExecute(AsyncTaskResult<JSONObject> outcome) {
        if (outcome instanceof MyOutObject) {
            MyOutObject result = (MyOutObject) outcome;
            // use the result
        } else if (outcome instanceof Exception) {
            Exception e = (Exception) outcome;
            // show error message
        } else throw new IllegalStateException();
    }
}
Matthias Ronge
fuente
1
completamente irrelevante
Dinu
-2

Si conoce la excepción correcta, puede llamar al

Exception e = null;

publishProgress(int ...);

p.ej:

@Override
protected Object doInBackground(final String... params) {

    // TODO Auto-generated method stub
    try {
        return mClient.call(params[0], params[1]);
    } catch(final XMLRPCException e) {

        // TODO Auto-generated catch block
        this.e = e;
        publishProgress(0);
        return null;
    }
}

y vaya a "onProgressUpdate" y haga lo siguiente

@Override
protected void onProgressUpdate(final Integer... values) {

    // TODO Auto-generated method stub
    super.onProgressUpdate(values);
    mDialog.dismiss();
    OptionPane.showMessage(mActivity, "Connection error", e.getMessage());
}

Esto será útil solo en algunos casos. También puede mantener una Global Exceptionvariable y acceder a la excepción.

Ajmal Muhammad P
fuente
1
Por favor, no hagas esto. ¡Eso es muy, muy mal estilo!
JimmyB