AsyncTask und Fehlerbehandlung unter Android

147

Ich konvertiere meinen Code von mit Handlerzu AsyncTask. Letzteres ist großartig in dem, was es tut - asynchrone Aktualisierungen und Verarbeitung der Ergebnisse im Haupt-UI-Thread. Was mir unklar ist, ist, wie man mit Ausnahmen umgeht, wenn etwas durcheinander gerätAsyncTask#doInBackground .

Ich mache es so, dass ich einen Fehlerhandler habe und Nachrichten an ihn sende. Es funktioniert gut, aber ist es der "richtige" Ansatz oder gibt es eine bessere Alternative?

Ich verstehe auch, dass der Fehlerhandler, wenn ich ihn als Aktivitätsfeld definiere, im UI-Thread ausgeführt werden sollte. Manchmal (sehr unvorhersehbar) erhalte ich jedoch eine Ausnahme, die besagt, dass der von ausgelöste Code Handler#handleMessageauf dem falschen Thread ausgeführt wird. Sollte ich Activity#onCreatestattdessen den Fehlerhandler initialisieren ? Das Einfügen runOnUiThreadin Handler#handleMessageerscheint überflüssig, wird jedoch sehr zuverlässig ausgeführt.

Bostone
quelle
Warum wollten Sie Ihren Code konvertieren? Gab es einen guten Grund?
HGPB
4
@ Haraldo es ist eine bessere Codierungspraxis, zumindest fühle ich mich so
Bostone

Antworten:

178

Es funktioniert gut, aber ist es der "richtige" Ansatz und gibt es eine bessere Alternative?

Ich halte an Throwableoder Exceptionin der AsyncTaskInstanz selbst fest und mache dann etwas damit onPostExecute(), sodass meine Fehlerbehandlung die Option hat, einen Dialog auf dem Bildschirm anzuzeigen.

CommonsWare
quelle
8
Brillant! Keine Notwendigkeit mehr, mit Handlern zu affen
Bostone
5
Sollte ich mich so an Throwable oder Exception halten? "Fügen Sie Ihrer eigenen AsyncTask-Unterklasse eine Instanzvariable hinzu, die das Ergebnis Ihrer Hintergrundverarbeitung enthält." Wenn Sie eine Ausnahme erhalten, speichern Sie die Ausnahme (oder einen anderen Fehlerstring / Code) in dieser Variablen. Überprüfen Sie beim Aufruf von onPostExecute, ob diese Instanzvariable auf einen Fehler gesetzt ist. Wenn ja, zeigen Sie eine Fehlermeldung an. "(Vom Benutzer" Streets of Boston " groups.google.com/group/android-developers/browse_thread/thread/… )
OneWorld
1
@ OneWorld: Ja, das sollte in Ordnung sein.
CommonsWare
2
Hallo CW, könnten Sie bitte Ihre Vorgehensweise näher erläutern - vielleicht mit einem kurzen Codebeispiel? Vielen Dank!!
Bruiser
18
@Bruiser: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/... hat ein AsyncTasknach dem Muster , das ich beschreiben.
CommonsWare
140

Erstellen Sie ein AsyncResult-Objekt (das Sie auch in anderen Projekten verwenden können).

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

Geben Sie dieses Objekt von Ihren AsyncTask doInBackground-Methoden zurück und überprüfen Sie es in postExecute. (Sie können diese Klasse als Basisklasse für Ihre anderen asynchronen Aufgaben verwenden.)

Unten finden Sie ein Modell einer Aufgabe, die eine JSON-Antwort vom Webserver erhält.

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
quelle
1
Ich mag das. Schöne Verkapselung. Da dies eine Paraphrase der ursprünglichen Antwort ist, bleibt die Antwort, aber dies verdient definitiv einen Punkt
Bostone
Dies ist eine ziemlich schöne Demonstration, wie nützlich Generika sein können. Es riecht seltsam in Bezug auf Komplexität, aber nicht in einer Weise, die ich wirklich artikulieren kann.
Nummer 1
4
Gute Idee, nur eine Frage: Warum rufst du super()an, AsyncTaskResultwenn die Klasse nichts erweitert?
Donturner
7
"no harm" - redundanter Code ist immer schädlich für die Lesbarkeit und Wartung. Hol es da raus! :)
Donturner
2
Die Lösung hat mir sehr gut gefallen ... ich habe darüber nachgedacht - die C # -Typen haben genau die gleiche Methode in der C #-entsprechenden nativen Implementierung von BackgroundTask verwendet ...
Vova
11

Wenn ich das Bedürfnis habe, Ausnahmen AsyncTaskrichtig zu behandeln, verwende ich dies als Superklasse:

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

}

Wie üblich überschreiben Sie doInBackgroundIhre Unterklasse, um Hintergrundarbeiten auszuführen, und lösen bei Bedarf gerne Ausnahmen aus. Sie müssen dann implementieren onPostExecute(weil es abstrakt ist), und dies erinnert Sie sanft daran, alle Arten von zu behandeln Exception, die als Parameter übergeben werden. In den meisten Fällen führen Ausnahmen zu einer Art UI-Ausgabe, daher onPostExecuteist dies der perfekte Ort, um dies zu tun.

Sulai
quelle
1
Whoa, warum nicht einfach paramsweiterleiten, damit es dem Original ähnlicher ist und einfacher zu migrieren ist?
TWiStErRob
@ TWiStErRob nichts falsch mit dieser Idee. Es ist eine Frage der persönlichen Präferenz, denke ich, da ich dazu neige, keine Parameter zu verwenden. Ich ziehe es new Task("Param").execute()vor new Task().execute("Param").
Sulai
5

Wenn Sie das RoboGuice-Framework verwenden möchten, das Ihnen weitere Vorteile bietet, können Sie die RoboAsyncTask ausprobieren, die über eine zusätzliche Callback onException () verfügt. Funktioniert wirklich gut und ich benutze es. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

ludwigm
quelle
Was ist deine Erfahrung damit? ziemlich stabil?
Nickaknudson
Lebt RoboGuicenoch? Scheint seit 2012 nicht mehr aktualisiert worden zu sein?
Dimitry K
Nein, RoboGuice ist tot und veraltet. Dagger2 ist der empfohlene Ersatz, aber es handelt sich nur um eine reine DI-Bibliothek.
Avi Cherry
3

Ich habe meine eigene AsyncTask-Unterklasse mit einer Schnittstelle erstellt, die Rückrufe für Erfolg und Misserfolg definiert. Wenn also eine Ausnahme in Ihrer AsyncTask ausgelöst wird, wird der Funktion onFailure die Ausnahme übergeben, andernfalls wird dem Rückruf onSuccess Ihr Ergebnis übergeben. Warum Android nichts Besseres zur Verfügung hat, ist mir ein Rätsel.

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
quelle
3

Eine umfassendere Lösung für Cagatay Kalan ist unten dargestellt:

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

Beispielaufgabe

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
quelle
Ich habe die Methode getResources () wie in myActivity.getApplicationContext (). GetResources ()
Stephane
2

Diese einfache Klasse kann Ihnen helfen

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
quelle
2

Eine andere Möglichkeit, die nicht von der Freigabe variabler Mitglieder abhängt, ist die Verwendung von Abbrechen.

Dies ist aus Android-Dokumenten:

public final boolean cancel (boolean mayInterruptIfRunning)

Versuche, die Ausführung dieser Aufgabe abzubrechen. Dieser Versuch schlägt fehl, wenn die Aufgabe bereits abgeschlossen wurde, bereits abgebrochen wurde oder aus einem anderen Grund nicht abgebrochen werden konnte. Wenn dies erfolgreich ist und diese Aufgabe beim Aufrufen von Abbrechen nicht gestartet wurde, sollte diese Aufgabe niemals ausgeführt werden. Wenn die Aufgabe bereits gestartet wurde, bestimmt der Parameter mayInterruptIfRunning, ob der Thread, der diese Aufgabe ausführt, unterbrochen werden soll, um die Aufgabe zu stoppen.

Das Aufrufen dieser Methode führt dazu, dass onCancelled (Object) im UI-Thread aufgerufen wird, nachdem doInBackground (Object []) zurückgegeben wurde. Durch Aufrufen dieser Methode wird garantiert, dass onPostExecute (Object) niemals aufgerufen wird. Nach dem Aufrufen dieser Methode sollten Sie den von isCancelled () von doInBackground (Object []) zurückgegebenen Wert regelmäßig überprüfen, um die Aufgabe so früh wie möglich abzuschließen.

Sie können also cancel in der catch-Anweisung aufrufen und sicherstellen, dass onPostExcute niemals aufgerufen wird, sondern onCancelled im UI-Thread aufgerufen wird. So können Sie die Fehlermeldung anzeigen.

Ali
quelle
Sie können die Fehlermeldung nicht richtig anzeigen, da Sie das Problem nicht kennen (Ausnahme). Sie müssen dennoch ein AsyncTaskResult abfangen und zurückgeben. Auch das Abbrechen eines Benutzers ist kein Fehler, sondern eine erwartete Interaktion: Wie unterscheiden Sie diese?
TWiStErRob
cancel(boolean)Dies führte zu einem Aufruf von onCancelled()exist von Anfang an, wurde jedoch onCancelled(Result)in API 11 hinzugefügt .
TWiStErRob
0

Tatsächlich verwendet AsyncTask FutureTask & Executor. FutureTask unterstützt die Ausnahmekette. Definieren wir zunächst eine Hilfsklasse

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

Zweitens verwenden wir

    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();
    }
Ja, ja
quelle
-2

Persönlich werde ich diesen Ansatz verwenden. Sie können einfach die Ausnahmen abfangen und den Stack-Trace ausdrucken, wenn Sie die Informationen benötigen.

Lassen Sie Ihre Aufgabe im Hintergrund einen booleschen Wert zurückgeben.

es ist so:

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

        @Override
                protected void onPostExecute(Boolean result) {

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

Eine andere Möglichkeit wäre, Objectals Rückgabetyp zu verwenden und onPostExecute()nach dem Objekttyp zu suchen. Es ist kurz.

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
quelle
1
völlig irrelevant
Dinu
-2

Wenn Sie die richtige Ausnahme kennen, können Sie die aufrufen

Exception e = null;

publishProgress(int ...);

z.B:

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

und gehen Sie zu "onProgressUpdate" und führen Sie die folgenden Schritte aus

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

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

Dies ist nur in einigen Fällen hilfreich. Sie können auch eine Global ExceptionVariable behalten und auf die Ausnahme zugreifen.

Ajmal Muhammad P.
quelle
1
Bitte tu das nicht. Das ist wirklich, wirklich, schlechter Stil!
JimmyB