Android中的AsyncTask和错误处理

148

我正在将代码从使用 Handler 改为 AsyncTask。后者在异步更新和在主UI线程中处理结果方面表现很好。但是,如果在 AsyncTask#doInBackground 中出现问题,我不清楚如何处理异常。

我的做法是创建一个错误处理程序并向其发送消息。这个方法运行良好,但是这是否是“正确”的方法或者是否有更好的替代方法?

此外,我知道如果将错误处理程序定义为 Activity 字段,则应在 UI 线程中执行。然而,有时(非常难以预测)我会收到一个异常,表示从 Handler#handleMessage 触发的代码正在错误的线程上执行。我应该在 Activity#onCreate 中初始化错误处理程序吗?将 runOnUiThread 放入 Handler#handleMessage 中似乎多余,但它运行得非常可靠。


你为什么想要转换你的代码?有充分的理由吗? - HGPB
4
@Haraldo,这是更好的编码实践,至少这就是我的感觉。 - Bostone
12个回答

178

它可以正常工作,但它是否是“正确”的方法,是否有更好的选择?

我在AsyncTask实例本身中保存ThrowableException,然后在onPostExecute()中对其进行操作,因此我的错误处理可以选择在屏幕上显示对话框。


8
太棒了!不需要再与Handlers纠缠了。 - Bostone
5
我应该如何持有 Throwable 或 Exception 对象?“在您自己的 AsyncTask 子类中添加一个实例变量,用于保存后台处理的结果。当出现异常时,将异常(或其他错误字符串/代码)存储在此变量中。当调用 onPostExecute 方法时,检查此实例变量是否设置为某个错误值。如果是,显示错误消息。”(来自用户 "Streets of Boston" http://groups.google.com/group/android-developers/browse_thread/thread/ffa3d9c589f8d753) - OneWorld
1
@OneWorld:是的,那应该没问题。 - CommonsWare
2
嗨CW,你能否请更详细地解释一下你的方法 - 也许可以用简短的代码示例吗?非常感谢!! - Bruiser
18
@Bruiser:https://github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/LunchList 中有一个遵循我所描述的模式的AsyncTask - CommonsWare
显示剩余7条评论

140
创建一个AsyncResult对象(您还可以在其他项目中使用)
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;
    }
}

从您的AsyncTask doInBackground方法中返回此对象,并在postExecute中进行检查。(您可以将此类用作其他异步任务的基类)

下面是一个模拟从Web服务器获取JSON响应的任务的示例。

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
我喜欢它。封装得很好。由于这是原始答案的释义,因此答案保持不变,但这绝对值得一分。 - Bostone
4
好的,明白了。在AsyncTaskResult类中并没有继承任何父类,为什么你要在其中调用super()方法呢?这是一个好主意,只是想问一下。 - donturner
7
“没有伤害” - 冗余代码总是有害于可读性和维护性。把它们删除掉! :) - donturner
2
真的很喜欢这个解决方案...仔细想想 - C# 的开发人员在 C# 对应的 BackgroundTask 本地实现中使用了完全相同的方法... - Vova
@Vova,你说得对。我开发.NET和Java应用程序,所以通常会从一个应用程序中复制一些有用的模式到另一个应用程序中。这个实现的灵感来自于.NET BackgroundWorker类。 - Cagatay Kalan
显示剩余2条评论

11

当我感觉需要适当处理AsyncTask中的异常时,我使用这个作为超类:

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

}

通常情况下,您可以在子类中覆盖doInBackground方法来执行后台工作,并在需要时抛出异常。然后,您被迫实现onPostExecute(因为它是抽象的),这会提醒您处理传递作为参数的各种类型的Exception。在大多数情况下,异常会导致某种类型的UI输出,因此onPostExecute是一个非常好的位置来处理它们。


1
哇,为什么不直接将“params”传递下去,这样更接近原始代码,并且更容易迁移? - TWiStErRob
@TWiStErRob,这个想法没有问题。我想这只是个人偏好的问题,因为我倾向于不使用参数。我更喜欢new Task("Param").execute()而不是new Task().execute("Param") - sulai

5

你对这个有什么经验?相当稳定吗? - nickaknudson
RoboGuice 还在维护吗?自 2012 年以来好像没有更新了? - Dimitry K
不,RoboGuice已经死亡和弃用了。Dagger2是推荐的替代品,但它只是一个基本的DI库。 - Avi Cherry

3

下面展示了对Cagatay Kalan解决方案的更全面的解决方案:

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

异常处理异步任务

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

示例任务

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

我使用了getResources()方法,就像myActivity.getApplicationContext().getResources()这样。 - Stephane

3
我使用一个带有接口的AsyncTask子类,用于定义成功和失败的回调函数。如果在您的AsyncTask中抛出异常,则会将该异常传递给onFailure函数,否则会将结果传递给onSuccess回调。为什么安卓没有更好的选择是我无法理解的。
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);
    }
}

2
这个简单的类可以帮助您。
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

另一种不依赖于变量成员共享的方法是使用cancel。

这来自安卓文档:

public final boolean cancel (boolean mayInterruptIfRunning)

试图取消执行此任务。如果该任务已经完成、已经被取消或由于其他原因无法取消,则此尝试将失败。如果成功,并且在调用cancel时此任务尚未启动,则此任务应永远不会运行。如果任务已经启动,则mayInterruptIfRunning参数确定是否应中断执行此任务的线程,以尝试停止任务。

调用此方法将导致在doInBackground(Object[])返回后在UI线程上调用onCancelled(Object)。调用此方法可确保不会调用onPostExecute(Object)。调用此方法后,您应定期检查从doInBackground(Object[])返回的isCancelled()的值,以尽早完成任务。

因此,您可以在catch语句中调用cancel,并确信不会调用onPostExecute,而是在UI线程上调用onCancelled。因此,您可以显示错误消息。


你无法正确显示错误消息,因为你不知道问题(异常),你仍然需要捕获并返回AsyncTaskResult。此外,用户取消不是错误,而是预期的交互:你如何区分它们? - TWiStErRob
cancel(boolean) 方法一直存在,会导致调用 onCancelled(),但 onCancelled(Result) 在 API 11 中被添加 - TWiStErRob

1
实际上,AsyncTask使用FutureTask和Executor,FutureTask支持异常链。
首先,让我们定义一个辅助类。
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
    }
}

第二,让我们使用。
    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();
    }

或者直接像下面这样使用FutureTask。
    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的内容: 在此输入图片描述

-2

就我个人而言,我会采用这种方法。 如果需要信息,您可以捕获异常并打印出堆栈跟踪。

使您的后台任务返回一个布尔值。

就像这样:

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

        @Override
                protected void onPostExecute(Boolean result) {

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接