AsyncTask и обработка ошибок на Android

Я конвертирую свой код с Handler на AsyncTask. Последний хорош в том, что делает - асинхронные обновления и обработка результатов в основном потоке пользовательского интерфейса. Мне неясно, как обрабатывать исключения, если в AsyncTask#doInBackground что-то пойдет не так.

Я использую обработчик ошибок и отправляю ему сообщения. Он работает нормально, но это «правильный» подход или есть лучшая альтернатива?

Также я понимаю, что если я определяю обработчик ошибок как поле Activity, он должен выполняться в потоке пользовательского интерфейса. Однако иногда (очень непредсказуемо) я получаю исключение, в котором говорится, что код, запускаемый из Handler#handleMessage, выполняется в неправильном потоке. Должен ли я вместо этого инициализировать обработчик ошибок в Activity#onCreate? Помещение runOnUiThread в Handler#handleMessage кажется излишним, но выполняется очень надежно.


person Bostone    schedule 16.11.2009    source источник
comment
Почему вы захотели преобразовать свой код? Была ли на то веская причина?   -  person HGPB    schedule 15.03.2013
comment
@Haraldo, это лучшая практика кодирования, по крайней мере, так я чувствую   -  person Bostone    schedule 15.03.2013


Ответы (12)


Он работает нормально, но является ли это «правильным» подходом и есть ли лучшая альтернатива?

Я держу Throwable или Exception в самом экземпляре AsyncTask, а затем что-то делаю с ним в onPostExecute(), поэтому моя обработка ошибок имеет возможность отображать диалоговое окно на экране.

person CommonsWare    schedule 16.11.2009
comment
Это ... это умно. Я сделаю это с этого момента. - person Eric Mill; 16.11.2009
comment
Блестяще! Больше не нужно обезьянничать с обработчиками - person Bostone; 16.11.2009
comment
Это способ, которым я должен придерживаться Throwable или Exception? Добавьте переменную экземпляра в свой собственный подкласс AsyncTask, который будет содержать результат вашей фоновой обработки. Когда вы получаете исключение, сохраните исключение (или другую строку / код ошибки) в этой переменной. Когда вызывается onPostExecute, посмотрите, установлена ​​ли для этой переменной экземпляра какая-либо ошибка. Если да, покажите сообщение об ошибке. (От пользователя Streets of Boston groups.google.com/group/android -developers / browse_thread / thread /) - person OneWorld; 12.10.2010
comment
@OneWorld: Да, все должно быть хорошо. - person CommonsWare; 12.10.2010
comment
Привет, CW, не могли бы вы объяснить свой способ сделать это более подробно, пожалуйста - может быть, с кратким примером кода? Большое спасибо!! - person Bruiser; 23.03.2011
comment
@Bruiser: github.com/commonsguy/cw-lunchlist/tree В / master / 15-Internet / есть AsyncTask шаблон, который я описываю. - person CommonsWare; 23.03.2011
comment
@CommonsWare: Спасибо за этот ответ. В качестве побочного примечания на случай, если вы хотите сделать что-то еще, за исключением отображения e.toString () в графическом интерфейсе. Можно определить частное перечисление в AsyncTask, а затем создать значение для каждого ожидаемого исключения и установить его в doInBackground (). В onPostExecute () можно включить это перечисление вместо того, чтобы делать if (e.equals (exception1)) ... Это то, что я делаю. Таким образом, я могу создавать различные диалоги ошибок графического интерфейса, специфичные для исключения. - person Vering; 14.01.2014
comment
Ссылки не работают. Я думаю, что OP хотел сказать, что у него есть некоторый логический атрибут is_exception_thrown_in_background_method, который он устанавливает в значение true, если это так, - затем он передает его своему обратному вызову, который он вызывает in onPostExecute (этот обратный вызов обычно является некоторым интерфейсом). Поскольку реализация этого обратного вызова определена в представлении, он может показать тост, текстовое представление или что-нибудь еще, говорящее об ошибке. Конечно, вместо простого логического можно использовать строку, содержащую исключение, или сам объект. @CommonsWare, не могли бы вы подтвердить мое сообщение, если у вас будет время? - person JarsOfJam-Scheduler; 02.05.2019
comment
@ JarsOfJam-Scheduler: Учитывая, что этому вопросу уже почти десять лет, и что AsyncTask довольно непопулярный сегодня, я не совсем уверен, почему вы беспокоитесь об этом. При этом ваше сообщение верное. - person CommonsWare; 02.05.2019
comment
@CommonsWare Я пробую аналогичную настройку со статической AsyncTask, как показано в вашем cw-ланчлисте на github. Для этого поля private FeedActivity activity = null; Android Studio выдает это предупреждение. Это поле пропускает объект контекста, говорящий, что статическое поле будет пропускать контексты. Есть ли решение этой проблемы? - person AJW; 28.05.2019

Создайте объект 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. (Вы можете использовать этот класс в качестве базового для других ваших асинхронных задач)

Ниже представлен макет задачи, которая получает ответ в формате 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
            }
        };

    }
person Cagatay Kalan    schedule 10.06.2011
comment
Мне это нравится. Хорошая инкапсуляция. Поскольку это перефразирование исходного ответа, ответ остается, но он определенно заслуживает очка. - person Bostone; 13.06.2011
comment
Это довольно хорошая демонстрация того, насколько полезными могут быть Generics. Это издает странный запах с точки зрения сложности, но не в том смысле, который я могу сформулировать. - person num1; 07.09.2011
comment
Хорошая идея, только один вопрос: почему вы вызываете super() в AsyncTaskResult, когда класс ничего не расширяет? - person donturner; 16.07.2012
comment
@donturner, я думаю, это то, что моя IDE автоматически ставит. Вы правы, super () вызывать не надо, но и вреда тоже :) - person Cagatay Kalan; 15.09.2012
comment
Никакого вреда - избыточный код всегда вреден для удобочитаемости и обслуживания. Убери это оттуда! :) - person donturner; 15.09.2012
comment
Решение действительно понравилось ... если подумать - ребята из C # использовали точно такой же метод в соответствующей собственной реализации BackgroundTask на C # ... - person Vova; 21.03.2013
comment
@Vova, ты прав. Я разрабатываю приложения как для .NET, так и для Java, поэтому обычно копирую некоторые полезные шаблоны из одного в другой. Вдохновением для этой реализации послужил класс .NET BackgroundWorker. - person Cagatay Kalan; 12.10.2016

Когда я чувствую необходимость правильно обрабатывать исключения в 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, которые передаются как параметр. В большинстве случаев исключения приводят к некоторому типу вывода пользовательского интерфейса, поэтому onPostExecute - идеальное место для этого.

person sulai    schedule 29.04.2013
comment
Почему бы просто не передать params вперед, чтобы он был более похож на исходный и легче переносился? - person TWiStErRob; 12.12.2014
comment
@TWiStErRob ничего плохого в этой идее. Думаю, это вопрос личных предпочтений, поскольку я обычно не использую параметры. Я предпочитаю new Task("Param").execute() new Task().execute("Param"). - person sulai; 12.12.2014

Если вы хотите использовать фреймворк RoboGuice, который дает вам другие преимущества, вы можете попробовать RoboAsyncTask, у которого есть дополнительный обратный вызов onException (). Работает очень хорошо, и я им пользуюсь. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

person ludwigm    schedule 08.08.2011
comment
каков твой опыт с этим? довольно стабильно? - person nickaknudson; 18.02.2013
comment
RoboGuice еще жив? Кажется, не обновлялся с 2012 года? - person Dimitry K; 04.04.2014
comment
Нет, RoboGuice больше не поддерживается. Рекомендуемая замена - Dagger2, но это только библиотека DI. - person Avi Cherry; 21.07.2017

Я создал свой собственный подкласс AsyncTask с интерфейсом, который определяет обратные вызовы для успеха и неудачи. Поэтому, если в вашей AsyncTask возникает исключение, функция onFailure передает исключение, в противном случае обратный вызов onSuccess передает ваш результат. Почему у Android нет чего-то лучшего, я не понимаю.

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);
    }
}
person ErlVolton    schedule 30.07.2014

Более полное решение для решения 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;
    }
}

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

Пример задачи

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);
    }
}
person vahapt    schedule 11.10.2014
comment
У меня есть метод getResources () как в myActivity.getApplicationContext (). GetResources () - person Stephane; 02.07.2015

Этот простой класс может помочь вам

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);
}
person Denis    schedule 10.01.2014

Другой способ, который не зависит от совместного использования переменных-членов, - это использовать cancel.

Это из документов Android:

public final boolean cancel (boolean mayInterruptIfRunning)

Попытки отменить выполнение этой задачи. Эта попытка не удастся, если задача уже завершена, отменена или не может быть отменена по какой-либо другой причине. В случае успеха, и эта задача не была запущена при вызове отмены, эта задача никогда не должна запускаться. Если задача уже запущена, то параметр mayInterruptIfRunning определяет, следует ли прервать поток, выполняющий эту задачу, при попытке остановить задачу.

Вызов этого метода приведет к вызову onCancelled (Object) в потоке пользовательского интерфейса после возврата doInBackground (Object []). Вызов этого метода гарантирует, что onPostExecute (Object) никогда не будет вызван. После вызова этого метода вы должны периодически проверять значение, возвращаемое isCancelled () из doInBackground (Object []), чтобы завершить задачу как можно раньше.

Таким образом, вы можете вызвать cancel в операторе catch и быть уверенным, что onPostExcute никогда не вызывается, а вместо этого onCancelled вызывается в потоке пользовательского интерфейса. Таким образом, вы можете показать сообщение об ошибке.

person Ali    schedule 24.07.2013
comment
Вы не можете правильно показать сообщение об ошибке, потому что вы не знаете проблему (исключение), вам все равно нужно поймать и вернуть AsyncTaskResult. Кроме того, отмена пользователем - это не ошибка, это ожидаемое взаимодействие: как вы их различите? - person TWiStErRob; 12.12.2014
comment
cancel(boolean), приводящий к вызову onCancelled(), существовал с самого начала, но onCancelled(Result) был добавлен в API 11. - person TWiStErRob; 12.12.2014

Собственно, 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();
    }
person Yessy    schedule 21.05.2019

Лично я воспользуюсь этим подходом. Вы можете просто перехватить исключения и распечатать трассировку стека, если вам нужна информация.

сделать вашу задачу в фоновом режиме возвращать логическое значение.

это вот так:

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

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}
person Harry    schedule 20.01.2011

Другая возможность - использовать Object в качестве возвращаемого типа и onPostExecute() проверить тип объекта. Это коротко.

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();
    }
}
person Matthias Ronge    schedule 13.02.2015
comment
совершенно неуместен - person Dinu; 09.03.2016

Если вы знаете правильное исключение, вы можете вызвать

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

и перейдите в «onProgressUpdate» и выполните следующие действия.

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

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

Это будет полезно только в некоторых случаях. Также вы можете сохранить переменную Global Exception и получить доступ к исключению.

person Ajmal Muhammad P    schedule 04.02.2012
comment
Пожалуйста, не делай этого. Это действительно, действительно, плохой стиль! - person JimmyB; 26.12.2013