148

I'm converting my code from using Handler to AsyncTask. The latter is great at what it does - asynchronous updates and handling of results in the main UI thread. What's unclear to me is how to handle exceptions if something goes haywire in AsyncTask#doInBackground.

The way I do it is to have an error Handler and send messages to it. It works fine, but is it the "right" approach or is there better alternative?

Also I understand that if I define the error Handler as an Activity field, it should execute in the UI thread. However, sometimes (very unpredictably) I will get an Exception saying that code triggered from Handler#handleMessage is executing on the wrong thread. Should I initialize error Handler in Activity#onCreate instead? Placing runOnUiThread into Handler#handleMessage seems redundant but it executes very reliably.

blahdiblah
  • 33,069
  • 21
  • 98
  • 152
Bostone
  • 36,858
  • 39
  • 167
  • 227

12 Answers12

178

It works fine but is it the "right" approach and is there better alternative?

I hold onto the Throwable or Exception in the AsyncTask instance itself and then do something with it in onPostExecute(), so my error handling has the option of displaying a dialog on-screen.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • That's...that's smart. I will do this from now on. – Eric Mill Nov 16 '09 at 04:33
  • 8
    Brilliant! No need to monkey with Handlers anymore – Bostone Nov 16 '09 at 05:47
  • 5
    Is this the way I should hold onto the Throwable or Exception? "Add an instance-variable to your own AsyncTask subclass that will hold the result of your background processing." When you get an exception, store the exception (or some other error-string/code) in this variable. When onPostExecute is called, see if this instance-variable is set to some error. If so, show an error message." (From the user "Streets of Boston" http://groups.google.com/group/android-developers/browse_thread/thread/ffa3d9c589f8d753) – OneWorld Oct 12 '10 at 15:06
  • 1
    @OneWorld: Yes, that should be fine. – CommonsWare Oct 12 '10 at 15:20
  • 2
    Hi CW, could you please explain your way of doing this in more detail please - maybe with a brief code example? Thanks a lot!! – Bruiser Mar 23 '11 at 14:10
  • 18
    @Bruiser: https://github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/LunchList has an `AsyncTask` following the pattern I describe. – CommonsWare Mar 23 '11 at 16:51
  • @CommonsWare: Thanks for this answer. As a side note in case you want to do something else with the exception than showing "e.toString()" in the GUI. One could define a private enum in the AsyncTask, and then make a value for each expected exception and set this in "doInBackground()". In "onPostExecute()", one can switch on this enum, instead of doing if (e.equals(exception1))... This is what I do. In this way I can make different GUI error dialogues specific for the exception. – Vering Jan 14 '14 at 13:51
  • https://github.com/commonsguy/cw-lunchlist/blob/d1799bcc96a6161de122f723c4e2597276b2215a/15-Internet/LunchList/src/apt/tutorial/FeedActivity.java#L72-L74 – Gayan Weerakutti Aug 29 '18 at 16:47
  • Links don't work. I think the OP wanted to say he has some boolean attribute "is_exception_thrown_in_background_method" that he sets to true if it's the case - then he passes it to its callback that he calls the in `onPostExecute` (this callback being some interface normally). Since the implementation of this callback is defined in the view, he can show a toast, a text view, or anything else saying an error occured. Of course, instead of a simple boolean one'd use a string containing the exception, or the object itself. @CommonsWare could you confirm my message if you have time please? – JarsOfJam-Scheduler May 02 '19 at 10:56
  • @JarsOfJam-Scheduler: Considering that this question is nearly a decade old, and that `AsyncTask` is fairly unpopular today, I am not quite certain why you are worrying about this. That being said, your message is correct. – CommonsWare May 02 '19 at 11:01
  • @CommonsWare I am trying a similar set up with a static AsyncTask as shown in your cw-lunchlist github. For this field, "private FeedActivity activity=null;" Android Studio gives this warning "This field leaks a context object saying a static field will leak contexts." Is there a solution to this issue? – AJW May 28 '19 at 03:06
  • "I am trying a similar set up with a static AsyncTask as shown in your cw-lunchlist github" -- that repo has been obsolete for years. "Is there a solution to this issue?" -- use more modern educational resources. [Here is a free older copy of one of my books](https://commonsware.com/Android/Android-6.7-CC.pdf), but it is *far* newer than the obsolete book tied to that obsolete GitHub repo. – CommonsWare May 28 '19 at 11:22
140

Create an AsyncResult object ( which you can also use in other projects)

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

Return this object from your AsyncTask doInBackground methods and check it in the postExecute. ( You can use this class as a base class for your other async tasks )

Below is a mockup of a task that gets a JSON response from the web server.

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

    }
FercoCQ
  • 139
  • 3
  • 14
Cagatay Kalan
  • 4,066
  • 1
  • 30
  • 23
  • 1
    I like it. Nice encapsulation. Since this is a paraphrase of the original answer the answer stays but this definitely deserves a point – Bostone Jun 13 '11 at 15:53
  • This is a pretty nice demonstration of how useful Generics can be. It's throwing a weird smell in terms of complexity, but not in a way I can really articulate. – num1 Sep 06 '11 at 20:05
  • 4
    Nice idea, just one question: why do you call `super()` in `AsyncTaskResult` when the class doesn't extend anything? – donturner Jul 15 '12 at 20:54
  • @donturner, I think it is what my IDE automatically put. You're right, no need to call the super() but no harm as well :) – Cagatay Kalan Sep 14 '12 at 22:48
  • 7
    "no harm" - redundant code is always harmful to readability and maintenance. Get it out of there! :) – donturner Sep 15 '12 at 10:06
  • 2
    Really liked the solution... coming to think about it - the C# guys used exactly the same method in the C# corresponding BackgroundTask native implementation... – Vova Mar 21 '13 at 15:57
  • @Vova, you are right. I develop both .NET and Java apps so I usually copy some useful patterns from one to the other. Inspiration for this implementation comes from the .NET BackgroundWorker class. – Cagatay Kalan Oct 12 '16 at 00:21
11

When I feel the need to handle Exceptions in AsyncTask properly, I use this as super class:

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

}

As normal, you override doInBackground in your subclass to do background work, happily throwing Exceptions where needed. You are then forced to implement onPostExecute (because it's abstract) and this gently reminds you to handle all types of Exception, which are passed as parameter. In most cases, Exceptions lead to some type of ui output, so onPostExecute is a perfect place to do that.

sulai
  • 5,204
  • 2
  • 29
  • 44
  • 1
    Whoa, why not just pass the `params` forward, so it's more similar to original and easier to migrate? – TWiStErRob Dec 12 '14 at 10:09
  • @TWiStErRob nothing wrong with that idea. It's a matter of personal preference I guess, as I tend to not use params. I prefer `new Task("Param").execute()` over `new Task().execute("Param")`. – sulai Dec 12 '14 at 11:34
5

If you want to use the RoboGuice framework which brings you other benefits you can try the RoboAsyncTask which has an extra Callback onException(). Works real good and I use it. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

ludwigm
  • 3,363
  • 3
  • 28
  • 36
3

I made my own AsyncTask subclass with an interface that defines callbacks for success and failure. So if an exception is thrown in your AsyncTask, the onFailure function gets passed the exception, otherwise the onSuccess callback gets passed your result. Why android doesn't have something better available is beyond 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);
    }
}
ErlVolton
  • 6,714
  • 2
  • 15
  • 26
3

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

Example Task

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);
    }
}
Community
  • 1
  • 1
vahapt
  • 1,685
  • 1
  • 21
  • 26
2

Another way that doesn't depend on variable member sharing is to use cancel.

This is from android docs:

public final boolean cancel (boolean mayInterruptIfRunning)

Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

Calling this method will result in onCancelled(Object) being invoked on the UI thread after doInBackground(Object[]) returns. Calling this method guarantees that onPostExecute(Object) is never invoked. After invoking this method, you should check the value returned by isCancelled() periodically from doInBackground(Object[]) to finish the task as early as possible.

So you can call cancel in catch statement and be sure that onPostExcute is never called, but instead onCancelled is invoked on UI thread. So you can show the error message.

Ali
  • 21,572
  • 15
  • 83
  • 95
  • You can't show the error message properly, because you don't know the problem (Exception), you still need to catch and return an AsyncTaskResult. Also a user cancelling is not an error, it's an expected interaction: How do you distinguish between these? – TWiStErRob Dec 12 '14 at 10:32
  • `cancel(boolean)` resulting in a call to `onCancelled()` existed since the beginning, but [`onCancelled(Result)` was added in API 11](http://developer.android.com/reference/android/os/AsyncTask.html#onCancelled(Result)). – TWiStErRob Dec 12 '14 at 10:50
2

This simple class can help you

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
  • 3,167
  • 1
  • 22
  • 23
1

Actually, AsyncTask use FutureTask & Executor, FutureTask support exception-chain

First let's define a helper class

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

Second, let's use

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

or use FutureTask directly like below

    FutureTask<?> futureTask = new FutureTask(() -> {throw new RuntimeException("Exception in TaskRunnable");}) {
        @Override
        protected void done() {
            super.done();
            //do something
            Log.d(TAG,"FutureTask done");
        }
    };

    AsyncTask.THREAD_POOL_EXECUTOR.execute(futureTask);

    try {
        futureTask.get();
    } catch (ExecutionException | InterruptedException e) {
        Log.d(TAG, "Detect exception in futureTask", e);
    }

logcat as below enter image description here

Yessy
  • 1,172
  • 1
  • 8
  • 13
-2

Another possibility would be to use Object as return type, and in onPostExecute() check for the object type. It is short.

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
  • 9,403
  • 7
  • 47
  • 63
-2

Personally, I will use this approach. You can just catch the exceptions and print out the stack trace if you need the info.

make your task in background return a boolean value.

it's like this:

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

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}
Harry
  • 89
  • 1
  • 6
-2

If you know the correct exception then you can call the

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

and go to "onProgressUpdate" and do the 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());
}

This will be helpful in some cases only. Also you can keep a Global Exception variable and access the exception.

Kirill
  • 2,590
  • 1
  • 17
  • 16