AsyncTask e gestione degli errori su Android


147

Sto convertendo il mio codice dall'uso Handlera AsyncTask. Quest'ultimo è eccezionale in ciò che fa: aggiornamenti asincroni e gestione dei risultati nel thread dell'interfaccia utente principale. Ciò che non è chiaro per me è come gestire le eccezioni se qualcosa va in tilt AsyncTask#doInBackground.

Il modo in cui lo faccio è avere un gestore degli errori e inviare messaggi ad esso. Funziona bene, ma è l'approccio "giusto" o esiste un'alternativa migliore?

Inoltre capisco che se definisco l'errore Handler come campo Activity, dovrebbe essere eseguito nel thread dell'interfaccia utente. Tuttavia, a volte (molto imprevedibilmente) riceverò un'eccezione che dice che il codice attivato da Handler#handleMessageviene eseguito sul thread errato. Dovrei invece inizializzare Error Handler in Activity#onCreate? La collocazione runOnUiThreadin Handler#handleMessagesembra ridondante ma si esegue in modo molto affidabile.


Perché hai voluto convertire il tuo codice? C'è stata una buona ragione?
HGPB,

4
@Haraldo è una pratica di programmazione migliore almeno così mi sento
Bostone

Risposte:


178

Funziona bene ma è l'approccio "giusto" e c'è un'alternativa migliore?

Ho tenere sul Throwableo Exceptionnel AsyncTaskistanza stessa e poi fare qualcosa con esso in onPostExecute(), quindi la mia gestione degli errori ha la possibilità di visualizzare una finestra sullo schermo.


8
Brillante! Non c'è più bisogno di fare la scimmia con Handlers
Bostone,

5
È questo il modo in cui dovrei aggrapparmi al gettabile o all'eccezione? "Aggiungi una variabile di istanza alla tua sottoclasse AsyncTask che conterrà il risultato dell'elaborazione in background." Quando ricevi un'eccezione, archivia l'eccezione (o qualche altra stringa / codice di errore) in questa variabile. Quando viene chiamato onPostExecute, vedere se questa variabile di istanza è impostata su un errore. In tal caso, mostra un messaggio di errore. "(Dall'utente" Streets of Boston " groups.google.com/group/android-developers/browse_thread/thread/… )
OneWorld

1
@OneWorld: Sì, dovrebbe andare bene.
CommonsWare,

2
Ciao CW, potresti per favore spiegare il tuo modo di farlo in modo più dettagliato per favore, magari con un breve esempio di codice? Molte grazie!!
Bruiser,

18
@Bruiser: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/… ha un AsyncTaskmodello seguente che descrivo.
CommonsWare

140

Crea un oggetto AsyncResult (che puoi usare anche in altri progetti)

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

Restituisci questo oggetto dai tuoi metodi doInBackground di AsyncTask e controllalo in postExecute. (Puoi usare questa classe come classe base per le altre tue attività asincrone)

Di seguito è riportato un mockup di un'attività che ottiene una risposta JSON dal server 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
            }
        };

    }

1
Mi piace. Incapsulamento piacevole. Poiché questa è una parafrasi della risposta originale, la risposta rimane, ma questo merita sicuramente un punto
Bostone,

Questa è una bella dimostrazione di quanto utile possa essere Generics. Sta emanando un odore strano in termini di complessità, ma non in un modo che posso davvero articolare.
num1,

4
Bella idea, una sola domanda: perché si chiama super()in AsyncTaskResultquando la classe non si estende nulla?
Donturner,

7
"nessun danno": il codice ridondante è sempre dannoso per la leggibilità e la manutenzione. Tiralo fuori di lì! :)
Donturner,

2
Mi è davvero piaciuta la soluzione ... venendo a pensarci bene - i ragazzi di C # hanno usato esattamente lo stesso metodo nell'implementazione nativa di BackgroundTask corrispondente in C # ...
Vova

11

Quando sento la necessità di gestire AsyncTaskcorrettamente le eccezioni , lo uso come superclasse:

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

}

Come di consueto, si doInBackgroundesegue l' override nella sottoclasse per eseguire il lavoro in background, generando felicemente Eccezioni dove necessario. Sei quindi costretto a implementare onPostExecute(perché è astratto) e questo ti ricorda delicatamente di gestire tutti i tipi di Exception, che vengono passati come parametro. Nella maggior parte dei casi, le eccezioni portano a un qualche tipo di output dell'interfaccia utente, quindi onPostExecuteè un posto perfetto per farlo.


1
Whoa, perché non passare semplicemente in paramsavanti, quindi è più simile all'originale e più facile da migrare?
TWiStErRob,

@TWiStErRob niente di sbagliato in quell'idea. È una questione di preferenze personali, immagino, poiché tendo a non usare i parametri. Preferisco new Task("Param").execute()finita new Task().execute("Param").
Sulai,

5

Se si desidera utilizzare il framework RoboGuice che offre altri vantaggi, è possibile provare RoboAsyncTask che ha un ulteriore Callback onException (). Funziona davvero bene e lo uso. http://code.google.com/p/roboguice/wiki/RoboAsyncTask


qual è la tua esperienza con questo? abbastanza stabile?
Nickaknudson,

È RoboGuiceancora vivo? Sembra non sia stato aggiornato dal 2012?
Dimitry K

No, RoboGuice è morto e deprecato. Dagger2 è la sostituzione consigliata, ma è solo una libreria DI bare-bones.
Avi Cherry,

3

Ho creato la mia sottoclasse AsyncTask con un'interfaccia che definisce i callback per esito positivo o negativo. Pertanto, se viene generata un'eccezione in AsyncTask, la funzione onFailure viene superata, altrimenti il ​​callback onSuccess viene superato il risultato. Perché Android non abbia qualcosa di meglio disponibile è oltre me.

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

Una soluzione più completa per Cagatay Kalan seguito viene mostrata :

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

Esempio di attività

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

Ho ottenuto il metodo getResources () come in myActivity.getApplicationContext (). GetResources ()
Stephane

2

Questa semplice lezione può aiutarti

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

Un altro modo che non dipende dalla condivisione dei membri variabili è utilizzare Annulla.

Questo proviene da documenti Android:

annullo booleano finale pubblico (booleano mayInterruptIfRunning)

Tenta di annullare l'esecuzione di questa attività. Questo tentativo fallirà se l'attività è già stata completata, è già stata annullata o non può essere annullata per qualche altro motivo. Se ha esito positivo e questa attività non è stata avviata quando viene chiamato Annulla, questa attività non dovrebbe mai essere eseguita. Se l'attività è già stata avviata, il parametro mayInterruptIfRunning determina se il thread che esegue questa attività deve essere interrotto nel tentativo di arrestare l'attività.

La chiamata a questo metodo comporterà l'invocazione di onCancelled (Object) sul thread dell'interfaccia utente dopo la restituzione di doInBackground (Object []). La chiamata a questo metodo garantisce che onPostExecute (Object) non venga mai richiamato. Dopo aver richiamato questo metodo, è necessario controllare periodicamente il valore restituito da isCancelled () da doInBackground (Object []) per completare l'attività il prima possibile.

Quindi puoi chiamare Annulla nell'istruzione catch e assicurarti che onPostExcute non venga mai chiamato, ma invece OnCancelled viene invocato sul thread dell'interfaccia utente. Quindi puoi mostrare il messaggio di errore.


Non è possibile mostrare correttamente il messaggio di errore, poiché non si conosce il problema (eccezione), è comunque necessario acquisire e restituire un AsyncTaskResult. Anche la cancellazione di un utente non è un errore, è un'interazione prevista: come si fa a distinguere tra questi?
TWiStErRob,

cancel(boolean)risultante in una chiamata onCancelled()esistita dall'inizio, ma è onCancelled(Result)stata aggiunta in API 11 .
TWiStErRob,

0

In realtà, AsyncTask usa FutureTask & Executor, FutureTask supporta la catena di eccezioni Prima di tutto definiamo una classe helper

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

Secondo, usiamo

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

-2

Personalmente, userò questo approccio. Puoi semplicemente catturare le eccezioni e stampare la traccia dello stack se hai bisogno delle informazioni.

rendere l'attività in background restituire un valore booleano.

è così:

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

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}

-2

Un'altra possibilità sarebbe quella di utilizzare Objectcome tipo restituito e sotto onPostExecute()controllo per il tipo di oggetto. È 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();
    }
}

1
completamente irrilevante
Dinu

-2

Se conosci l'eccezione corretta, puoi chiamare il

Exception e = null;

publishProgress(int ...);

per esempio:

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

e vai su "onProgressUpdate" e fai il folowing

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

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

Questo sarà utile solo in alcuni casi. Inoltre puoi mantenere una Global Exceptionvariabile e accedere all'eccezione.


1
Per favore, non farlo. Questo è davvero, davvero, cattivo stile!
JimmyB,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.