web-dev-qa-db-fra.com

AsyncTask et gestion des erreurs sur Android

Je convertis mon code en utilisant Handler en AsyncTask. Ce dernier est excellent dans ce qu'il fait - mises à jour asynchrones et traitement des résultats dans le thread principal de l'interface utilisateur. Ce qui ne me semble pas clair, c’est comment gérer les exceptions si quelque chose se passe mal dans AsyncTask#doInBackground.

La façon dont je le fais est d’avoir un gestionnaire d’erreur et de lui envoyer des messages. Cela fonctionne bien, mais est-ce la "bonne" approche ou existe-t-il une meilleure alternative?

De plus, je comprends que si je définis le gestionnaire d'erreurs comme un champ d'activité, il doit s'exécuter dans le thread d'interface utilisateur. Cependant, parfois (très imprévisible), je vais avoir une exception disant que le code a été déclenché par Handler#handleMessage s'exécute sur le mauvais thread. Dois-je initialiser le gestionnaire d'erreurs dans Activity#onCreate au lieu? Placer runOnUiThread dans Handler#handleMessage semble superflu, mais il s’exécute de manière très fiable.

146
Bostone

Cela fonctionne bien, mais est-ce la "bonne" approche et existe-t-il une meilleure alternative?

Je conserve le Throwable ou Exception dans l'instance AsyncTask elle-même, puis je fais quelque chose avec dans onPostExecute(), afin que ma gestion des erreurs dispose de l'option suivante: afficher une boîte de dialogue à l'écran.

174
CommonsWare

Créez un objet AsyncResult (que vous pouvez également utiliser dans d'autres projets)

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

Renvoyez cet objet à partir de vos méthodes AsyncTask doInBackground et vérifiez-le dans postExecute. (Vous pouvez utiliser cette classe comme classe de base pour vos autres tâches asynchrones.)

Vous trouverez ci-dessous une maquette d'une tâche qui obtient une réponse JSON du serveur 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
            }
        };

    }
135
Cagatay Kalan

Lorsque je ressens le besoin de gérer correctement les exceptions dans AsyncTask, je l'utilise comme super classe:

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

}

Comme d'habitude, vous écrasez doInBackground dans votre sous-classe pour effectuer du travail d'arrière-plan, en lançant avec bonheur des exceptions si nécessaire. Vous êtes alors obligé d'implémenter onPostExecute (parce que c'est abstrait) et cela vous rappelle gentiment de gérer tous les types de Exception, qui sont passés en paramètre. Dans la plupart des cas, les exceptions conduisent à un type de sortie de l'interface utilisateur. onPostExecute est donc l'endroit idéal pour le faire.

10
sulai

Si vous souhaitez utiliser le framework RoboGuice qui vous apporte d'autres avantages, vous pouvez essayer RoboAsyncTask qui dispose d'un Callback supplémentaire sur une exception (). Fonctionne vraiment bien et je l'utilise. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

5
ludwigm

J'ai créé ma propre sous-classe AsyncTask avec une interface qui définit les rappels en cas de succès et d'échec. Ainsi, si une exception est levée dans votre AsyncTask, l'exception est transmise à la fonction onFailure, sinon le rappel onSuccess est transmis à votre résultat. Pourquoi Android ne dispose pas de quelque chose de mieux disponible, cela me dépasse.

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
ErlVolton

Une solution plus complète à la solution de Cagatay Kalan est présentée ci-dessous:

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

Exemple de tâche

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
vahapt

Une autre manière qui ne dépend pas du partage de membres variables consiste à utiliser cancel.

Cela provient de Android docs:

annulation finale publique booléenne (boolean mayInterruptIfRunning)

Tente d'annuler l'exécution de cette tâche. Cette tentative échouera si la tâche est déjà terminée, a déjà été annulée ou ne peut pas être annulée pour une autre raison. En cas de succès et si cette tâche n'a pas démarré lorsque l'annulation est appelée, cette tâche ne doit jamais être exécutée. Si la tâche a déjà démarré, le paramètre mayInterruptIfRunning détermine si le thread qui l'exécute doit être interrompu pour tenter de l'arrêter.

L'appel de cette méthode entraînera l'appel de onCancelled (Object) sur le thread d'interface utilisateur après le retour de doInBackground (Object []). L'appel de cette méthode garantit que onPostExecute (Object) n'est jamais appelé. Après avoir appelé cette méthode, vous devez vérifier périodiquement la valeur renvoyée par isCancelled () depuis doInBackground (Object []) pour terminer la tâche le plus tôt possible.

Vous pouvez donc appeler cancel dans l'instruction catch et vous assurer que onPostExcute n'est jamais appelé, mais onCancelled est appelé sur le thread d'interface utilisateur. Ainsi, vous pouvez afficher le message d'erreur.

2
Ali

Ce cours simple peut vous aider

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
Denis

En fait, AsyncTask utilise FutureTask & Executor, FutureTask prend en charge la chaîne d'exception. Définissons d'abord une classe d'assistance.

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

Deuxièmement, utilisons

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