AsyncTask y manejo de errores en Android


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

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

También entiendo que si defino el manejador de errores como un campo de actividad, debe ejecutarse en el subproceso de interfaz de usuario. Sin embargo, a veces (muy impredecible) obtendré una excepción diciendo que el código activado desde Handler#handleMessage se está ejecutando en el subproceso incorrecto. ¿Debo inicializar el controlador de errores en Activity#onCreate en su lugar? Colocar runOnUiThread en Handler#handleMessage parece redundante, pero se ejecuta de forma muy fiable.

Author: blahdiblah, 2009-11-16

11 answers

Funciona bien, pero es el " derecho" enfoque y hay mejor alternativa?

Me aferro a Throwable o Exception en la propia instancia AsyncTask y luego hago algo con ella en onPostExecute(), por lo que mi manejo de errores tiene la opción de mostrar un diálogo en la pantalla.

 170
Author: CommonsWare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-16 02:05:05

Crea un objeto AsyncResult (que también puedes 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;
    }
}

Devuelve este objeto desde tus métodos AsyncTask doInBackground y revísalo en el postExecute. (Puede usar esta clase como clase base para sus otras tareas asincrónicas)

A continuación se muestra una maqueta de una tarea que recibe 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
            }
        };

    }
 127
Author: Cagatay Kalan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-04 13:35:32

Cuando siento la necesidad de manejar las Excepciones en AsyncTask correctamente, uso esto como super clase:

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, sobrescribes doInBackground en tu subclase para hacer trabajo en segundo plano, lanzando alegremente Excepciones donde sea necesario. Luego se ve obligado a implementar onPostExecute (porque es abstracto) y esto le recuerda suavemente que debe manejar 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 onPostExecute es un lugar perfecto para hacerlo.

 10
Author: sulai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-27 14:30:23

Si quieres usar el framework RoboGuice que te trae otros beneficios puedes probar el RoboAsyncTask que tiene una devolución de llamada adicional onException(). Funciona muy bien y lo uso. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

 5
Author: ludwigm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-08 17:35:02

Hice mi propia subclase AsyncTask con una interfaz que define callbacks para el éxito y el fracaso. Por lo tanto, si se lanza una excepción en su AsyncTask, la función onFailure recibe la excepción, de lo contrario la devolución de llamada onuccess recibe el 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);
    }
}
 3
Author: ErlVolton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-30 16:50:21

A more comprehensive solution to Cagatay Kalan's solution is shown below:

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);
    }
}
 3
Author: vahapt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:13

Esta sencilla clase 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);
}
 2
Author: Denis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-10 13:28:42

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

Esto es de android docs:

Public final boolean cancel (boolean mayInterruptIfRunning)

Intenta cancelar la ejecución de esta tarea. Este intento fallará si la tarea ya se ha completado, ya ha sido cancelado, o no podría ser cancelado por alguna otra razón. Si éxito, y esta tarea no se ha iniciado cuando se llama a cancel, esto tarea nunca debe ejecutar. Si la tarea ya ha comenzado, entonces el mayInterruptIfRunning parámetro determina si el hilo la ejecución de esta tarea debe interrumpirse en un intento de detener tarea.

Llamar a este método dará lugar a que se invoque onCancelled(Object) en el subproceso de interfaz de usuario después de doInBackground(Object[]) devuelve. Llamando a esto método garantiza que onPostExecute (Objeto) nunca se invoca. Despues invocando este método, debe verificar el valor devuelto por isCancelled() periódicamente desde doInBackground (Objeto []) para terminar el tarea tan pronto como sea posible.

Así que puede llamar a cancel en la instrucción catch y asegurarse de que onPostExcute nunca se llame, sino que onCancelled se invoque en el subproceso de la interfaz de usuario. Para que pueda mostrar el mensaje de error.

 2
Author: Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-14 21:11:54

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

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

Es así:

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

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}
 -2
Author: Harry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-20 05:40:02

Otra posibilidad sería usar Object como tipo de retorno, y en onPostExecute() comprobar el tipo de objeto. Es corto.

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();
    }
}
 -2
Author: Paramaeleon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-13 07:35:38

Si conoce la excepción correcta, puede llamar al

Exception e = null;

publishProgress(int ...);

Eg:

@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 solo será útil en algunos casos. También puede mantener un Global Exception variable y acceder a la excepción.

 -2
Author: Ajmal Muhammad P,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-19 09:05:58